Предыдущие части:

«Геометрическая головоломка на выходные»,
«Электродинамика виртуальной Вселенной»,
«Механика виртуальной Вселенной»,
«Квантовая механика виртуальной Вселенной (Часть I)»,
«Квантовая механика виртуальной Вселенной (Часть II)»
«Релятивизм виртуальной Вселенной»
«Космология виртуальной Вселенной (Часть I)»
«Космология виртуальной Вселенной (Часть II)»
«Электричество, проводимость и сверхпроводимость в виртуальной Вселенной»
«Атом в Виртуальной Вселенной (Часть I)»
«Атом в Виртуальной Вселенной (Часть II)»
«Атом в Виртуальной Вселенной (Часть III) [Химия]»

Здравствуйте, мои уважаемые читатели.

Следующим шагом я хотел приступить к описанию ядра атома в рамках описанной ранее теории. Но по комментариям и при личном обсуждении, пришёл к выводу, что теория хоть и является минималистичной, но всё-же, интуитивному её пониманию сильно мешает то, что всё обсуждение строится в 3+1 геометрических измерениях. С одной стороны — их не 11, как в теории суперструн, но и 4 — это сложно для понимания для неподготовленного человека. Да и, кого я обманываю — даже подготовленному проще оперировать формулами, чем образами в пространствах, размерностью выше трёх. Но в этой модели очень важно понимать её онтологию, суть процесса. Формулы являются лишь языком, позволяющим (вот тут будет тавтология) описать формализм системы и дать возможность оценить её качественно и количественно.

Эти размышления привели меня к мысли о необходимости дать расширенное визуальное описание системы. Я не придумал ничего лучше, чем понизить размерность. Исходно, у нас система представляет собой трёхмерную сферу S3. А давайте рассмотрим такую же модель, но на сфере S2. Да, удастся показать не всё — например, спин 1/2 здесь показать не выйдет. Но кое что должно проявиться и дать интуицию.

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

Сразу исповедаюсь — я не специалист в python, он мне едва знаком (хотя, Монти Пайтон, в своё время — доставил :)) — мне ближе и роднее C/C++, PHP, Java. И, разумеется, я не знаком с его библиотеками для математики и визуализации. Мой грех в том, что я воспользовался ИИ для написания скриптов (и это было нелегко, вопреки содержимому бравурных статей о «Вайб Кодинге», но стоит отметить, что в итоге ИИ справился неплохо, судя по картинке). Скриптов с более-менее честными вычислениями и чтобы любопытные исследователи могли залезть в код, чтобы покрутить и «пощупать» модель. Просто, разумной альтернативы python я не нашёл. Возможно, я что-то упустил — не судите строго. Я действительно попытался донести идею максимально образно. Исходники буду вставлять в неизменном виде (возможно, с моими минорными правками, но оригинальные строки оставлю закомментированными; и я просил ИИ оставлять содержательные комментарии к коду).

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

Итак, засучим рукава и приступим.

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

Давайте, для начала выведем нашу двумерную систему.

Фазовый лагранжиан в 2+1D

Пусть в каждой точке мира определён фазовый вектор

n(x^{\mu}) \in S^2, \lvert n \rvert = 1,

где x^{\mu}=(t,x,y), а сама фаза является отображением

n:R^{2,1} \to S^2.

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

Интуитивно — у нас есть сфера, как глобус. Это замкнутая двумерная поверхность, в трёхмерном пространстве. В каждой точке этой поверхности существует фаза U(1) — окружность, которая вращается. Эту вращающуюся окружность мы можем выразить неким виртуальным вектором, перпендикулярным плоскости её вращения. Кстати, если Вы знаете Геометрическую Алгебру и Вам что-то показалось знакомым — Вам не показалось. Лучше всего эта модель описывается на языке Геометрической Алгебры. Но это уже лирика. Итак, эта стрелочка своим концом может описывать точно такую же сферу S2, как и ту, на которой она находится, только меньшего масштаба.

Естественный лагранжиан для такого поля имеет вид нелинейной сигма-модели:

L=\tfrac{\kappa}{2}(\partial_{\mu}n)^2 + \tfrac{\alpha}{4}(\partial_{\mu}n \times \partial_{\nu}n)^2−\mu V(n)

Здесь: \kappa — фазовая жёсткость, \partial_{\mu}=(\partial_t, \partial_x, \partial_y), обычная сигма-модель, отвечает за распространение, волны, фотоны — без него нет вообще динамики. \alpha-член — скрученная энергия — это член Скирма, делает вихри устойчивыми, без него электрон “схлопывается”, а так же задаёт вклад в энергию конфигурации. Далее мы будем трактовать этот вклад как источник инертной массы и гравитационного потенциала. \mu-член — потенциал вакуума \mu V(n), фиксирует направление фазы в вакууме, задаёт масштаб энергий, делает возможным “спокойное состояние”. (\partial_{\mu}n)^2≡\partial_{\mu}n\cdot \partial^{\mu}nпри сигнатуре (+,−,−).

Пояснение: Деление на 2 и 4 не несёт самостоятельного физического смысла. Эти коэффициенты являются стандартной нормировкой, обеспечивающей корректный учёт симметрий при вариации действия. В частности, множитель 1/2 компенсирует двойной счёт квадратичных производных, а множитель 1/4 — комбинаторную кратность членов, содержащих антисимметричные произведения производных. То-есть, эти коэффициенты не вводят новой физики и не несут дополнительного смысла. Они лишь фиксируют удобную систему отсчёта для измерения энергии фазовой деформации.

Этот лагранжиан описывает чистую геометрию фазового поля без каких-либо внешних потенциалов или источников.

Опять же, интуитивно — в отсутствии каких-либо возбуждений и полей, первые два члена равны нулю, остаётся третий, который и задаёт предпочтительное направление фазы, как перпендикулярное нашей Мировой поверхности сферы S2 и она выглядит, как ёж (помните про вектор, которым мы описываем фазу?).

Ток Маурера–Картана

В полной 3+1D теории используется левый ток Маурера-Картана. В SU(2)-формализме ток — U^{−1}\partial_{\mu}U, в редукции на единичный вектор n удобно использовать эквивалентный so(3)-объект n\times \partial_{\mu}n.

Это вводится как удобная замена, то-есть — вместо самого поля n (этих самых окружностей или стрелочек) удобно ввести so(3)-ток:

J_{\mu}=n \times \partial_{\mu}n

Этот объект всегда ортогонален n (существует, как проекция на нашу Мировую поверхность и вообще, существует в касательном пространстве; но, прошу заметить — это только ради удобства и не вводит новых степеней свободы).

Пояснение: Проекция фазового вектора на мировую плоскость естественным образом приводит к току Маурера–Картана J_{\mu}. Стоит отметить, что в полной SU(2)-версии модели используется именно левая форма этого тока, а не правая. Это связано с тем, что левая форма описывает изменение фазы относительно фиксированной системы отсчёта мира, то есть отражает реальное пространственно-временное перераспределение фазовой структуры. Правая форма, напротив, соответствует внутреннему «переопределению» фазы и не связана с физическим переносом или деформацией структуры в пространстве.

Замечание: Для поля n \in S^2 тождественно сохраняется топологический ток:

j_{top}^{\mu}=\tfrac{1}{8\pi}\epsilon^{\mu\nu\rho}n\cdot(\partial_{\nu}n \times \partial_{\rho}n), \partial_{\mu}j_{top}^{\mu}=0

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

Через этот ток лагранжиан переписывается в эквивалентной форме:

L=\tfrac{\kappa}{2}J_{\mu} J^{\mu}  + \tfrac{\alpha}{4} (n\cdot(J_{\mu} \times J_{\nu}))^2  − \mu V(n)

Таким образом, вся динамика системы выражается не через «потенциалы» или «поля», а через геометрию фазового тока.

Давайте теперь опишем фотон с помощью того, что у нас получилось.

Глобальная фаза и электромагнитное возбуждение

Как я уже говорил выше, в нашей модели вакуум — это не пустое пространство, а глобально эволюционирующая фазовая структура. В каждой точке физического пространства существует внутренняя фазовая степень свободы, глобальная эволюция которой определяет физическое время. Вакуум соответствует однородному вращению фазы вокруг выделенной («световой») оси, выбираемой потенциальным членом V(U) — тот самый третий член уравнения в Лагранжиане.

Электромагнитная волна не является материальным объектом, а представляет собой локальное возмущение этой глобальной фазовой динамики. То-есть, фотон соответствует временному избытку или дефициту фазовой скорости вращения по сравнению с вакуумным фоном. Если совсем грубо — фаза крутится с определённой \omega_{\ast}, мы её немного «пинаем», чтобы она крутилась чуть быстрее (или медленнее), добавляем некоторую \Delta\omega. Поскольку функция фазы на всей поверхности старается сохранять гладкость — это возбуждение передаётся соседям и соседям соседей и далее.

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

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

Дисклеймер: Я не умею записывать анимацию, потому буду вставлять картинку из анимации. Но саму динамику можно будет посмотреть, запустив python-скрипт


 Фиг.1 «капля в пруд»
Фиг.1 «капля в пруд»

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

Скрытый текст
"""
S² / O(3) sigma-model: "drop in a pond" (single impulse) + OPEN boundaries + E/B visualization
============================================================================================

What this script demonstrates (popular S²-interface, close to your core idea):
  • Calm vacuum: n(x,y) = (0,0,1) everywhere (red arrows point straight up).
  • A SINGLE excitation at t=0: we do NOT permanently deform the center.
    Instead we give a short, localized *initial velocity* (an impulse), like a droplet hitting a pond.
    After the wave passes, the interior returns to calm vacuum (like the pond surface).
  • Circular outward wave on a local plane patch of the "world sphere".
  • OPEN (radiating) boundaries: Mur/Sommerfeld 1st-order absorbing BC, to prevent reflections.
  • EM-like fields from geometry (U(1) fiber over S²):
        n ↦ angles (θ, φ)
        a_vec = (1 - cosθ) ∇φ
        a_0   = (1 - cosθ) ∂t φ
        E = -∂t a_vec - ∇a_0        (blue in-plane arrows)
        Bz = (∇×a_vec)_z            (green heatmap + optional vertical arrows)

Why this is "honest through the Lagrangian":
  Dynamics of n are derived from the sigma-model Lagrangian (no Skyrme term here):
      L = (κ/2) ( |∂t n|^2 - c^2 |∇n|^2 ),  with |n|=1.
  Variation gives the constrained wave equation:
      ∂t² n - c² ∇² n = λ n,
  and we enforce the constraint by normalizing n after each step (standard numerical approach).

Notes:
  • We choose an impulse that is *circularly polarized* in the plane near the center,
    so that the geometric connection has a nontrivial curvature (visible B).
  • This is a visualization demo, not a high-precision PDE solver.
  • If you want even "more open" boundaries, enlarge Lx,Ly or lower eps / increase sigma.

Dependencies: numpy, matplotlib
Run: python s2_open_drop_em_demo.py
"""

import numpy as np
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation
from mpl_toolkits.mplot3d import Axes3D  # noqa: F401


# ----------------------------
# Viewer / presentation settings
# ----------------------------
VIEW_ELEV = 90
VIEW_AZIM = -60

FIGSIZE = (11, 8)

# Arrow colors
COLOR_N = "red"       # n field
COLOR_E = "blue"      # E field
# B is shown as a green heatmap; optional vertical arrows use green tones


# ----------------------------
# Simulation parameters
# ----------------------------
Nx, Ny = 140, 140      # grid resolution
Lx, Ly = 14.0, 14.0    # physical size of patch (bigger => less boundary influence)
c = 1.0
kappa = 1.0            # overall scale (doesn't change wave shapes here)

dx = Lx / Nx
dy = Ly / Ny

# CFL stability (conservative)
dt = 0.22 * min(dx, dy) / (c * np.sqrt(2))

T_total = 7.0
steps_per_frame = 3
nframes = int(T_total / (dt * steps_per_frame))

# Visualization density
q_step_n = 7           # subsample step for n quiver
q_step_E = 7           # subsample step for E quiver

# Arrow lengths (visual only)
LEN_N = 0.65
LEN_E = 0.75

# Optional: show vertical B arrows (can clutter). Heatmap is already good.
SHOW_B_ARROWS = False
q_step_B = 9
LEN_B = 0.55

# Impulse ("drop") parameters
eps = 0.85             # impulse strength (increase carefully)
sigma = 0.70           # impulse width
# If eps is too high, the sigma-model nonlinearity + constraint can generate artifacts.
# Better: keep eps moderate, increase sigma for smoother wave.


# ----------------------------
# Grid
# ----------------------------
x = np.linspace(-Lx/2, Lx/2, Nx, endpoint=False)
y = np.linspace(-Ly/2, Ly/2, Ny, endpoint=False)
X, Y = np.meshgrid(x, y, indexing="ij")
Z = np.zeros_like(X)

# Subsample grids for quivers
XsN = X[::q_step_n, ::q_step_n]
YsN = Y[::q_step_n, ::q_step_n]
ZsN = Z[::q_step_n, ::q_step_n]

XsE = X[::q_step_E, ::q_step_E]
YsE = Y[::q_step_E, ::q_step_E]
ZsE = Z[::q_step_E, ::q_step_E]

XsB = X[::q_step_B, ::q_step_B]
YsB = Y[::q_step_B, ::q_step_B]
ZsB = Z[::q_step_B, ::q_step_B]


# ----------------------------
# Open-boundary finite differences (no wrap)
# ----------------------------
def laplacian_open(f):
    """
    2D Laplacian on an open domain:
      - interior: central second differences
      - edges: set to 0 here (handled by absorbing BC after time step)
    """
    lap = np.zeros_like(f)
    lap[1:-1, 1:-1] = (
        (f[2:, 1:-1] - 2*f[1:-1, 1:-1] + f[:-2, 1:-1]) / dx**2 +
        (f[1:-1, 2:] - 2*f[1:-1, 1:-1] + f[1:-1, :-2]) / dy**2
    )
    return lap


def grad_open(f):
    """
    2D gradient on an open domain:
      - interior: central differences
      - edges: one-sided differences
    """
    dfdx = np.empty_like(f)
    dfdy = np.empty_like(f)

    dfdx[1:-1, :] = (f[2:, :] - f[:-2, :]) / (2*dx)
    dfdx[0, :] = (f[1, :] - f[0, :]) / dx
    dfdx[-1, :] = (f[-1, :] - f[-2, :]) / dx

    dfdy[:, 1:-1] = (f[:, 2:] - f[:, :-2]) / (2*dy)
    dfdy[:, 0] = (f[:, 1] - f[:, 0]) / dy
    dfdy[:, -1] = (f[:, -1] - f[:, -2]) / dy

    return dfdx, dfdy


def unwrap_phase_2d(phi):
    """Phase unwrap to reduce π-jumps (demo-level)."""
    pu = np.unwrap(phi, axis=0)
    pu = np.unwrap(pu, axis=1)
    return pu


def safe_angles_from_n(n):
    nx, ny, nz = n[..., 0], n[..., 1], n[..., 2]
    nz = np.clip(nz, -1.0, 1.0)
    theta = np.arccos(nz)
    phi = np.arctan2(ny, nx)
    return theta, phi


# ----------------------------
# Mur / Sommerfeld absorbing boundary condition (1st order)
# ----------------------------
rx = (c*dt - dx) / (c*dt + dx)
ry = (c*dt - dy) / (c*dt + dy)


def apply_mur_absorbing_bc(n_next, n_curr):
    """
    Mur absorbing BC for outgoing waves on edges, applied componentwise.

    Left edge (i=0):   u0^{n+1} = u1^{n} + rx (u1^{n+1} - u0^{n})
    Right edge:        uN^{n+1} = u_{N-1}^{n} + rx (u_{N-1}^{n+1} - uN^{n})
    Similarly for y edges with ry.
    """
    # x edges
    n_next[0, :, :] = n_curr[1, :, :] + rx * (n_next[1, :, :] - n_curr[0, :, :])
    n_next[-1, :, :] = n_curr[-2, :, :] + rx * (n_next[-2, :, :] - n_curr[-1, :, :])

    # y edges
    n_next[:, 0, :] = n_curr[:, 1, :] + ry * (n_next[:, 1, :] - n_curr[:, 0, :])
    n_next[:, -1, :] = n_curr[:, -2, :] + ry * (n_next[:, -2, :] - n_curr[:, -1, :])

    # corners: average neighbors to reduce corner artifacts
    n_next[0, 0, :] = 0.5 * (n_next[1, 0, :] + n_next[0, 1, :])
    n_next[0, -1, :] = 0.5 * (n_next[1, -1, :] + n_next[0, -2, :])
    n_next[-1, 0, :] = 0.5 * (n_next[-2, 0, :] + n_next[-1, 1, :])
    n_next[-1, -1, :] = 0.5 * (n_next[-2, -1, :] + n_next[-1, -2, :])


# ----------------------------
# Dynamics from the sigma-model Lagrangian (leapfrog)
# ----------------------------
def step_sigma_model_open(n_curr, n_prev):
    """
    Leapfrog update for:
        ∂t² n - c² ∇² n = λ n,  |n|=1

    Unconstrained wave step:
        n_next = 2 n_curr - n_prev + dt² c² ∇² n_curr

    Then:
      - apply absorbing (Mur) boundary on edges
      - renormalize to enforce |n|=1 (constraint)
    """
    lapx = laplacian_open(n_curr[..., 0])
    lapy = laplacian_open(n_curr[..., 1])
    lapz = laplacian_open(n_curr[..., 2])

    n_next = np.empty_like(n_curr)
    n_next[..., 0] = 2*n_curr[..., 0] - n_prev[..., 0] + (dt**2) * (c**2) * lapx
    n_next[..., 1] = 2*n_curr[..., 1] - n_prev[..., 1] + (dt**2) * (c**2) * lapy
    n_next[..., 2] = 2*n_curr[..., 2] - n_prev[..., 2] + (dt**2) * (c**2) * lapz

    apply_mur_absorbing_bc(n_next, n_curr)

    # Enforce constraint |n|=1 everywhere
    norm = np.linalg.norm(n_next, axis=2, keepdims=True)
    n_next /= (norm + 1e-12)

    return n_next

# ----------------------------
# Geometric U(1) connection and fields E, B
# ----------------------------
def compute_connection_EB(n, n_prev):
    theta, _phi = safe_angles_from_n(n)
    theta_p, _phi_p = safe_angles_from_n(n_prev)

    nx, ny = n[..., 0], n[..., 1]
    nxp, nyp = n_prev[..., 0], n_prev[..., 1]

    # spatial derivatives of nx, ny
    dnx_dx, dnx_dy = grad_open(nx)
    dny_dx, dny_dy = grad_open(ny)

    # time derivatives of nx, ny
    dnx_dt = (nx - nxp) / dt
    dny_dt = (ny - nyp) / dt

    # stable phase-derivatives (NO atan2 / unwrap)
    denom = nx*nx + ny*ny + 1e-12
    dphi_dx = (nx * dny_dx - ny * dnx_dx) / denom
    dphi_dy = (nx * dny_dy - ny * dnx_dy) / denom
    dphi_dt = (nx * dny_dt - ny * dnx_dt) / denom

    fac = (1.0 - np.cos(theta))

    ax = fac * dphi_dx
    ay = fac * dphi_dy
    a0 = fac * dphi_dt

    # For ∂t a we need previous a as well
    nx2, ny2 = nxp, nyp
    dnxp_dx, dnxp_dy = grad_open(nx2)
    dnyp_dx, dnyp_dy = grad_open(ny2)
    dnxp_dt = (nxp - nx) / dt
    dnyp_dt = (nyp - ny) / dt
    denom_p = nx2*nx2 + ny2*ny2 + 1e-12

    dphi_p_dx = (nx2 * dnyp_dx - ny2 * dnxp_dx) / denom_p
    dphi_p_dy = (nx2 * dnyp_dy - ny2 * dnxp_dy) / denom_p
    dphi_p_dt = (nx2 * dnyp_dt - ny2 * dnxp_dt) / denom_p

    fac_p = (1.0 - np.cos(theta_p))
    ax_p = fac_p * dphi_p_dx
    ay_p = fac_p * dphi_p_dy
    a0_p = fac_p * dphi_p_dt

    dax_dt = (ax - ax_p) / dt
    day_dt = (ay - ay_p) / dt

    da0_dx, da0_dy = grad_open(a0)

    Ex = -dax_dt - da0_dx
    Ey = -day_dt - da0_dy

    day_dx, _ = grad_open(ay)
    _, dax_dy = grad_open(ax)
    Bz = day_dx - dax_dy

    return Ex, Ey, Bz


# ----------------------------
# Initialize: calm vacuum + ONE impulse ("drop")
# ----------------------------
n = np.zeros((Nx, Ny, 3), dtype=float)
n[..., 2] = 1.0  # vacuum

# Single impulse: give an initial "velocity" v in the tangent plane (XY).
# This is the "drop hits pond" moment: no permanent deformation at t=0, only a kick.
r2 = X**2 + Y**2
vamp = eps * np.exp(-r2 / (2*sigma**2))

# Circular polarization of the impulse in the plane:
# alpha is the azimuth around center; v points tangentially (rotational impulse).
# This tends to produce a clean EM-like curvature signal (B).
alpha = np.arctan2(Y, X)
vx = -vamp * np.sin(alpha)
vy = +vamp * np.cos(alpha)
vz = 0.0

# Leapfrog start: n_prev = n - dt * v
n_prev = n.copy()
n_prev[..., 0] -= dt * vx
n_prev[..., 1] -= dt * vy
# n_prev[..., 2] unchanged (vz=0)

# Project back to S² (constraint)
n_prev /= (np.linalg.norm(n_prev, axis=2, keepdims=True) + 1e-12)


# ----------------------------
# Visualization setup
# ----------------------------
fig = plt.figure(figsize=FIGSIZE)
ax = fig.add_subplot(111, projection="3d")

wf_step = 12  # plane wireframe density

ax.view_init(elev=VIEW_ELEV, azim=VIEW_AZIM)
ax.set_xlim(-Lx/2, Lx/2)
ax.set_ylim(-Ly/2, Ly/2)
ax.set_zlim(-1.1, 2.0)   # allow downward B arrows if enabled
ax.set_xlabel("x")
ax.set_ylabel("y")
ax.set_zlabel("z (normal)")

# For a stable B heatmap scaling, keep a running robust percentile estimate
B_scale = 1e-6


def draw_frame(frame_idx):
    global n, n_prev, B_scale

    # Integrate
    for _ in range(steps_per_frame):
        n_next = step_sigma_model_open(n, n_prev)
        n_prev, n = n, n_next

    # Compute fields
    Ex, Ey, Bz = compute_connection_EB(n, n_prev)

    # Robust scaling for E and B display
    # (prevents "jumping" arrows/colormap due to a single outlier)
    Emag = np.sqrt(Ex**2 + Ey**2)
    E95 = np.percentile(Emag, 95) + 1e-12

    B95 = np.percentile(np.abs(Bz), 95) + 1e-12
    # Slowly update B_scale for smooth visualization
    B_scale = 0.9 * B_scale + 0.1 * B95

    # Subsample for arrows
    ns = n[::q_step_n, ::q_step_n, :]
    Exs = Ex[::q_step_E, ::q_step_E]
    Eys = Ey[::q_step_E, ::q_step_E]

    # For E arrows: keep amplitude (not just direction), but clip to [-1,1] in display units
    Exd = np.clip(Exs / E95, -1.0, 1.0)
    Eyd = np.clip(Eys / E95, -1.0, 1.0)

    # Clear & redraw
    ax.cla()
    ax.view_init(elev=VIEW_ELEV, azim=VIEW_AZIM)
    ax.set_xlim(-Lx/2, Lx/2)
    ax.set_ylim(-Ly/2, Ly/2)
    ax.set_zlim(-1.1, 2.0)
    ax.set_xlabel("x")
    ax.set_ylabel("y")
    ax.set_zlabel("z (normal)")

    t = frame_idx * steps_per_frame * dt
    ax.set_title(
        f"Single 'drop' impulse on S²-field (OPEN boundaries).  t={t:5.2f}\n"
        f"n: red (unit),  E: blue (clipped @95%),  Bz: green heatmap (±95%)",
        pad=18
    )

    # Plane wireframe
    ax.plot_wireframe(
        X[::wf_step, ::wf_step], Y[::wf_step, ::wf_step], Z[::wf_step, ::wf_step],
        linewidth=0.5, alpha=0.22
    )

    # --- B heatmap on the plane (green palette) ---
    # Use a diverging colormap but with greenish feel:
    # We'll map negative to light green, positive to dark green by custom normalization via 'Greens'
    # However Greens is one-sided; better use 'PiYG' or 'PRGn'—but you asked green/light-green.
    # We'll emulate by plotting two surfaces: positive and negative with different greens.
    B = Bz.copy()
    Bn = np.clip(B / (B_scale + 1e-12), -1.0, 1.0)

    # Positive part (0..1) darker green
    Bpos = np.clip(Bn, 0.0, 1.0)
    # Negative part (0..1) lighter green (use smaller alpha)
    Bneg = np.clip(-Bn, 0.0, 1.0)

    # Surface colors
    cmap = plt.cm.Greens
    # Positive: stronger alpha
    fc_pos = cmap(0.25 + 0.75 * Bpos)
    fc_pos[..., 3] = 0.38 * Bpos  # alpha
    # Negative: very light green
    fc_neg = cmap(0.05 + 0.35 * Bneg)
    fc_neg[..., 3] = 0.22 * Bneg  # alpha

    ax.plot_surface(X, Y, Z, facecolors=fc_pos, rstride=1, cstride=1, shade=False)
    ax.plot_surface(X, Y, Z, facecolors=fc_neg, rstride=1, cstride=1, shade=False)

    # --- n field (red arrows) ---
    ax.quiver(
        XsN, YsN, ZsN,
        ns[..., 0], ns[..., 1], ns[..., 2],
        length=LEN_N, normalize=True,
        color=COLOR_N, linewidth=0.7
    )

    # --- E field (blue arrows, in-plane) ---
    ax.quiver(
        XsE, YsE, ZsE,
        Exd, Eyd, np.zeros_like(Exd),
        length=LEN_E, normalize=False,
        color=COLOR_E, linewidth=0.9
    )

    # --- Optional: B arrows up/down (can clutter) ---
    if SHOW_B_ARROWS:
        Bzs = Bz[::q_step_B, ::q_step_B]
        pos = Bzs > 0
        neg = Bzs < 0

        if np.any(pos):
            ax.quiver(
                XsB[pos], YsB[pos], ZsB[pos],
                0*XsB[pos], 0*YsB[pos], 1+0*ZsB[pos],
                length=LEN_B, normalize=True,
                color="green", linewidth=1.0
            )
        if np.any(neg):
            ax.quiver(
                XsB[neg], YsB[neg], ZsB[neg],
                0*XsB[neg], 0*YsB[neg], -1+0*ZsB[neg],
                length=LEN_B, normalize=True,
                color="#7CFC90", linewidth=1.0
            )

    return []


anim = FuncAnimation(fig, draw_frame, frames=nframes, interval=30, blit=False, repeat=True)
plt.show()

Смотрите — векторы электрического поля перпендикулярны направлению распространения волны, так как сонаправлены циркуляции фазовой энергии. Фазовая энергия вращения, благодаря сигма члену \kappa (скирмовский член \alpha здесь не учитывается) — колеблется, а не просто уменьшается экспоненциально. Магнитное поле является ротором вектора электрического поля и направлено перпендикулярно Мировой поверхности. Но, поскольку поверхность у нас двумерная — на этой поверхности магнитное поле является точкой, скаляром — длиной этого вектора со знаком плюс или минус (зелёный/светло-зелёный).

А теперь — ещё интересная штука (ну, чтобы два раза не вставать, коль уж мы говорим о фотоне)

Красное смещение как эволюция времени, а не растяжение пространства

В стандартной космологии красное смещение трактуется как растяжение длин волн в расширяющемся пространстве. В нашей модели эта интерпретация принципиально иная.

Здесь фотон сохраняет свою внутреннюю фазовую структуру в процессе распространения. Изменяется не он сам, а скорость течения физического времени. Физическое время определяется операционально — через глобальную фазовую эволюцию вакуума. По мере изменения глобального радиуса кривизны Вселенной R0 меняется и характерная фазовая частота \omega(T), определяющая все внутренние процессы.

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

а)
а)
б)
б)

Фиг.2 «красное смещение»
а) фотон достиг детектора early б) фотон достиг детектора late

Код скрипта под катом

Скрытый текст
"""
2D demo: phase-redshift from global SU(2) frequency drift (S² local patch)
=========================================================================

Goal (as in your сosmology logic):
  • We DO NOT "stretch space". We keep a local S² patch ≈ plane (x,y).
  • A narrowband Gaussian wavepacket ("photon") propagates across the patch.
  • The global curvature radius R0(T) slowly increases with global parameter T.
  • The local phase frequency scale of *all internal excitations* drifts as
        ω(T) ∝ 1 / R0(T)
    (this is your cosmology assumption).
  • Operational / proper time t is related to global parameter T by an inverse law:
        dt/dT = ω* / ω(T)          (this is the only way to get dt_obs/dt_em = ω_em/ω_obs)
    so when ω decreases, clocks run slower and measured photon frequency redshifts.

Physics from a Lagrangian (kept explicit in code):
  We propagate a scalar "phase excitation" field ψ(x,y,T) with a time-dependent
  wave speed in global parameter T, derived by mapping a constant-c wave equation
  in operational time t into the global parameter T.

  Start from standard wave Lagrangian in operational time t:
      L = (χ/2) [ (∂t ψ)^2 - c^2 |∇ψ|^2 ].

  Convert derivatives using dt/dT = ω*/ω(T)  ⇒  ∂t = (ω(T)/ω*) ∂T.
  Plug in (slow-variation approximation; we neglect terms ~ dω/dT inside ∂t²):
      (∂t ψ)^2 = (ω/ω*)^2 (∂T ψ)^2.

  Up to the same approximation, the Euler-Lagrange equation becomes:
      ∂T² ψ - c_eff(T)^2 ∇² ψ = 0
  with
      c_eff(T) = c * (ω(T)/ω*).

  This makes the packet's global oscillation rate drift ∝ ω(T),
  and the measured frequency in operational time t redshifts as ω drops.

Visualization:
  • Show ψ(x,y) as a colormap (2D).
  • Two detectors at fixed x positions record ψ(T) and estimate the frequency ν(t)
    using zero-crossings, but with time converted to operational time t.
  • We print R0(T), ω(T), and the running ν_A, ν_B, and z_est ≈ ν_A/ν_B - 1.

Notes:
  • This is a "clean didactic" simulation, not a full SU(2) field solver.
  • Boundaries are made "open enough" with a sponge (absorbing layer) to kill reflections.
  • Animation is intentionally slow/smooth: low steps_per_frame, longer interval.

Dependencies: numpy, matplotlib
Run: python phase_redshift_2d_demo.py
"""

import numpy as np
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation


# ----------------------------
# 0) User-facing "constants" (publishable knobs)
# ----------------------------

# Viewer / speed
FPS = 30                 # animation fps target (visual only)
INTERVAL_MS = int(1000 / FPS)

steps_per_frame = 2      # smaller => slower, smoother
show_every_n_frames = 1  # keep 1 for smooth updates

# Local patch geometry (S² ≈ plane)
Nx, Ny = 360, 120
Lx, Ly = 24.0, 8.0
dx, dy = Lx / Nx, Ly / Ny

# Wave physics (operational / local time t)
c = 1.0                  # physical propagation speed in operational time t
chi = 1.0                # Lagrangian prefactor (does not affect shape here)

# Cosmology knobs: global radius and phase frequency drift
R0_init = 1.0
dR0_dT  = 0.015          # positive => expansion in global parameter T

omega_star = 1.0         # universal reference frequency ω*
omega0 = 1.0             # sets ω(T)=omega0/R0(T)

# IMPORTANT relation (your cosmology-consistent sign):
#   dt/dT = ω* / ω(T)   so that  dt_obs/dt_em = ω_em/ω_obs
# (This is the inverse relation dt ∝ 1/ω mentioned in your cosmology text.)

# Wavepacket ("photon") parameters (narrowband Gaussian beam)
A = 1.0
k0 = 7.5                 # carrier spatial wavenumber along +x
sigma_x = 1.4            # packet width
sigma_y = 0.8
x0 = -0.35 * Lx          # launch position
y0 = 0.0

# Detectors
x_det_A = -0.10 * Lx     # "earlier" (packet hits first)
x_det_B = +0.30 * Lx     # "later"  (packet hits second)
y_det   = 0.0

# Frequency estimation settings
min_abs_signal = 0.010    # ignore tiny values (avoid noise crossings)
need_crossings = 2       # crossings needed before we show a stable frequency

# Absorbing boundary (sponge) settings
sponge_w = 30            # sponge thickness in grid cells
sponge_strength = 0.035  # damping per global step (tune if reflections appear)


# ----------------------------
# 1) Grid
# ----------------------------
x = np.linspace(-Lx / 2, Lx / 2, Nx, endpoint=False)
y = np.linspace(-Ly / 2, Ly / 2, Ny, endpoint=False)
X, Y = np.meshgrid(x, y, indexing="ij")


def idx_from_x(xx):
    return int(np.clip(np.round((xx + Lx/2) / dx), 0, Nx-1))


def idx_from_y(yy):
    return int(np.clip(np.round((yy + Ly/2) / dy), 0, Ny-1))


ixA, iyA = idx_from_x(x_det_A), idx_from_y(y_det)
ixB, iyB = idx_from_x(x_det_B), idx_from_y(y_det)


# ----------------------------
# 2) Cosmology: R0(T), ω(T), mapping dt/dT
# ----------------------------
T = 0.0     # global evolution parameter
t_op = 0.0  # operational / proper time (the one clocks use)

def R0_of_T(T):
    # simplest: linear growth (you can replace with your ODE for R(T) later)
    return R0_init + dR0_dT * T

def omega_of_T(T):
    # your cosmology assumption: ω(T) ∝ 1/R(T)
    return omega0 / R0_of_T(T)

def dt_dT(T):
    # the crucial inverse relation for time dilation & redshift in your cosmology:
    # dt/dT = ω* / ω(T)
    return omega_star / omega_of_T(T)

def c_eff_of_T(T):
    # mapping constant-c wave propagation in operational time t into global parameter T:
    # ∂t = (ω/ω*) ∂T  ⇒  wave speed in T is c_eff = c * (ω/ω*)
    return c * (omega_of_T(T) / omega_star)


# ----------------------------
# 3) Stable explicit scheme for ∂T²ψ - c_eff(T)² ∇²ψ = 0
# ----------------------------

# Conservative dt in global parameter T (CFL wrt maximal c_eff)
# since ω(T) decreases as R grows, c_eff decreases over time (stability improves),
# so we set dt_global based on initial c_eff.
c_eff0 = c_eff_of_T(0.0)
dt_global = 0.28 * min(dx, dy) / (c_eff0 * np.sqrt(2))

def laplacian_open(f):
    lap = np.zeros_like(f)
    lap[1:-1, 1:-1] = (
        (f[2:, 1:-1] - 2*f[1:-1, 1:-1] + f[:-2, 1:-1]) / dx**2 +
        (f[1:-1, 2:] - 2*f[1:-1, 1:-1] + f[1:-1, :-2]) / dy**2
    )
    return lap


# Sponge mask (0 inside, increasing toward edges)
sponge = np.zeros((Nx, Ny), dtype=float)
for i in range(Nx):
    di = min(i, Nx-1-i)
    if di < sponge_w:
        sponge[i, :] += (sponge_w - di) / sponge_w
for j in range(Ny):
    dj = min(j, Ny-1-j)
    if dj < sponge_w:
        sponge[:, j] += (sponge_w - dj) / sponge_w
sponge = np.clip(sponge, 0.0, 1.0)


def apply_sponge(psi, psi_prev):
    # simple velocity damping in the sponge layer
    # v ≈ (psi - psi_prev)/dt  →  damp v near boundaries
    damp = np.exp(-sponge_strength * sponge)
    v = (psi - psi_prev)
    v *= damp
    psi_prev[:, :] = psi - v


# ----------------------------
# 4) Initial condition: a right-moving narrowband Gaussian packet
# ----------------------------
# In 1D, right-moving is ψ(x,0)=A cos(kx) exp(-(x-x0)^2/2σ^2)
# with ∂tψ = +c ∂xψ at t=0.
# We do the same here (beam in 2D) and convert ∂t to ∂T using ∂t=(ω/ω*)∂T.

envelope = np.exp(-((X - x0)**2) / (2*sigma_x**2) - ((Y - y0)**2) / (2*sigma_y**2))
carrier = np.cos(k0 * (X - x0))
psi = A * envelope * carrier

# spatial derivative for right-moving condition
dpsi_dx = np.zeros_like(psi)
dpsi_dx[1:-1, :] = (psi[2:, :] - psi[:-2, :]) / (2*dx)
dpsi_dx[0, :] = (psi[1, :] - psi[0, :]) / dx
dpsi_dx[-1, :] = (psi[-1, :] - psi[-2, :]) / dx

# ∂tψ(0) = +c ∂xψ  (right-moving)
dpsi_dt0 = -c * dpsi_dx

# Convert to ∂Tψ using ∂t = (ω/ω*) ∂T  ⇒  ∂Tψ = (ω*/ω) ∂tψ
omega_init = omega_of_T(0.0)
dpsi_dT0 = (omega_star / omega_init) * dpsi_dt0

# Leapfrog init: psi_prev = psi - dt_global * ∂Tψ
psi_prev = psi - dt_global * dpsi_dT0


# ----------------------------
# 5) Frequency estimator at detectors (zero-crossings in operational time t)
# ----------------------------
class Detector:
    def __init__(self, name, ix, iy):
        self.name = name
        self.ix = ix
        self.iy = iy
        self.prev_val = None
        self.prev_t = None
        self.cross_times_t = []  # operational time of (positive-slope) zero-crossings

    def update(self, psi_val, t_now):
        # record a positive-slope zero crossing: ψ crosses from negative to positive
        # ignore tiny |ψ| to avoid numerical chatter far from the packet
        if self.prev_val is None:
            self.prev_val = psi_val
            self.prev_t = t_now
            return

        if abs(psi_val) < min_abs_signal and abs(self.prev_val) < min_abs_signal:
            self.prev_val = psi_val
            self.prev_t = t_now
            return

        if self.prev_val < 0.0 and psi_val >= 0.0:
            # linear interpolation to estimate crossing time
            dv = psi_val - self.prev_val
            if abs(dv) > 1e-12:
                frac = -self.prev_val / dv
            else:
                frac = 0.0
            t_cross = self.prev_t + frac * (t_now - self.prev_t)

            # store only if it is not a duplicate (numerical safety)
            if len(self.cross_times_t) == 0 or (t_cross - self.cross_times_t[-1]) > 1e-6:
                self.cross_times_t.append(t_cross)

        self.prev_val = psi_val
        self.prev_t = t_now

    def frequency_hz(self):
        # one full period corresponds to two successive positive-slope zero crossings
        # (because each such crossing happens once per cycle)
        if len(self.cross_times_t) < need_crossings:
            return None
        # use last few periods for smoothing
        times = np.array(self.cross_times_t[-need_crossings:])
        dt = np.diff(times)
        # dt is the period estimate
        Tavg = np.mean(dt)
        if Tavg <= 0:
            return None
        return 1.0 / Tavg


detA = Detector("A", ixA, iyA)
detB = Detector("B", ixB, iyB)


# ----------------------------
# 6) Animation (2D)
# ----------------------------
fig, ax = plt.subplots(figsize=(12, 4.2))
ax.set_title("Phase-redshift demo (S² local patch ≈ plane)")

im = ax.imshow(
    psi.T,
    origin="lower",
    extent=[-Lx/2, Lx/2, -Ly/2, Ly/2],
    aspect="auto",
    cmap="coolwarm",
    vmin=-0.8, vmax=0.8,
    interpolation="bilinear"
)

# detector markers
ax.plot([x_det_A], [y_det], "ko", ms=6)
ax.text(x_det_A, y_det + 0.35, "A (early)", color="k", ha="center", va="bottom")

ax.plot([x_det_B], [y_det], "ko", ms=6)
ax.text(x_det_B, y_det + 0.35, "B (late)", color="k", ha="center", va="bottom")

ax.set_xlabel("x (local patch)")
ax.set_ylabel("y (local patch)")

# HUD text
hud = ax.text(
    0.02, 0.97, "",
    transform=ax.transAxes,
    ha="left", va="top",
    fontsize=10,
    family="monospace",
    bbox=dict(boxstyle="round", facecolor="white", alpha=0.85, edgecolor="none")
)

plt.tight_layout()


def step_once():
    global psi, psi_prev, T, t_op

    # time-dependent effective speed in global parameter T
    c_eff = c_eff_of_T(T)

    # integrate operational time (this is the "clock time" used for measurement)
    t_op += dt_dT(T) * dt_global

    # leapfrog step: ψ_next = 2ψ - ψ_prev + dt² c_eff² ∇²ψ
    lap = laplacian_open(psi)
    psi_next = 2.0 * psi - psi_prev + (dt_global**2) * (c_eff**2) * lap

    # sponge to suppress reflections (acts on boundary velocity)
    apply_sponge(psi_next, psi)

    # shift time layers
    psi_prev, psi = psi, psi_next

    # advance global parameter
    T += dt_global


def update(frame):
    # run a few physics steps per rendered frame for smoothness
    for _ in range(steps_per_frame):
        step_once()

        # detector updates in operational time t_op
        detA.update(psi[detA.ix, detA.iy], t_op)
        detB.update(psi[detB.ix, detB.iy], t_op)

    # update image
    im.set_data(psi.T)

    # compute current cosmology values
    R0 = R0_of_T(T)
    w  = omega_of_T(T)

    # detector frequencies (in operational time)
    nuA = detA.frequency_hz()
    nuB = detB.frequency_hz()

    # estimated redshift from detector readings (if both exist)
    z_est = None
    if (nuA is not None) and (nuB is not None) and nuB > 0:
        z_est = (nuA / nuB) - 1.0

    # HUD text (keep it readable and stable)
    lines = [
        f"Global parameter T      = {T:7.3f}",
        f"Operational time t       = {t_op:7.3f}",
        f"R0(T)                    = {R0:7.4f}    (set by dR0/dT = {dR0_dT})",
        f"ω(T) = ω0/R0(T)          = {w:7.4f}",
        f"dt/dT = ω*/ω(T)          = {dt_dT(T):7.4f}",
        f"c_eff(T) = c·ω/ω*        = {c_eff_of_T(T):7.4f}",
        "",
        f"Detector A freq νA(t)    = {nuA:7.3f}  Hz" if nuA is not None else "Detector A freq νA(t)    =  (waiting crossings...)",
        f"Detector B freq νB(t)    = {nuB:7.3f}  Hz" if nuB is not None else "Detector B freq νB(t)    =  (waiting crossings...)",
        f"z_est ≈ νA/νB - 1        = {z_est:7.3f}" if z_est is not None else "z_est ≈ νA/νB - 1        =  (waiting...)",
        "",
        "Interpretation: as R0 grows → ω drops → ν(t) drops (redshift) in operational time.",
    ]
    hud.set_text("\n".join(lines))

    return (im, hud)


anim = FuncAnimation(
    fig, update,
    interval=INTERVAL_MS,
    blit=False,
    repeat=True
)

plt.show()

То-есть, та самая фаза, которая есть вращающаяся окружность U(1) в каждой точке Мировой поверхности и которую мы договорились выражать через вектор, который перпендикулярен плоскости вращения этой фазы — меняет скорость вращения в зависимости от радиуса Вселенной. Таким образом, когда Вселенная расширяется, вращение фазы замедляется и мы наблюдаем красное смещение. Когда Вселенная перестанет расширяться — мы не будем наблюдать какое либо смещение, а если начнёт сжиматься — мы увидим УФ-смещение. Кстати, фактически, если сильно глубоко погружаться в философию — то самое глобальное время задаёт пространство. Не будет вращения фазы — не будет и пространства. Но расширение здесь не метрическое (локальная плотность пространства не изменяется).

Ну, а теперь перейдём к структуре посложнее.

Электрон как топологический фазовый дефект

Если фотон соответствует распространяющемуся фазовому возбуждению, то массивные частицы связаны с локализованными топологическими структурами самого фазового поля, где фаза уже крутится вокруг «тяжёлых» направлений и наклонена по отношению к мировой поверхности. То есть, электрон — это не точечный объект, а устойчивый фазовый вихрь, вокруг топологического дефекта. Здесь, ядро электронного вихря (топологический дефект) не соответствует возбуждённому или «сжатому» состоянию вакуума. Напротив, оно представляет собой область, в которой сам фазовый параметр порядка перестаёт быть определённым, отмечая локальный разрыв фазового описания вакуума. Фактически, если очень упрощённо — в центре находится неупорядоченная фаза вакуума (метафорически — «первозданный вакуум»), ничто, где эффективное описание на языке n \in S^2 разрушается; можно мыслить это как \lvert \Psi \rvert \to 0 для параметра порядка, из-за чего коэффициенты в эффективном лагранжиане деградируют.

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

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

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

 Фиг. 3 «электрон без внутреннего вращения»
Фиг. 3 «электрон без внутреннего вращения»

Код скрипта под катом. Так же можно поменять угол зрения и тип отображения скирмионного вихря Блох/Неель.

Скрытый текст
"""
Static "electron" in 2+1D as a phase vortex on an S² field (visualized in 3D over a plane)
=========================================================================================

What you get:
  • A static topological vortex/defect n(x,y) ∈ S² (red arrows) over a 2D plane patch.
  • An "electric-like" field E (blue arrows) computed as E = -∇Φ from an effective scalar
    potential Φ derived from the field's deviation from the vacuum direction.
  • A "gravitational potential / time dilation" map on the plane (colormap) derived from
    the energy density of the vortex (sigma-model energy).

Notes (important):
  • In 2+1D, "magnetic field" is naturally a pseudoscalar Bz; here we focus on a static object.
  • A strictly static Berry-connection EM construction gives E=0 if a0=0.
    For a charged static object we need an effective scalar potential. In the reduced S² toy
    model we take Φ ∝ (1 - n_z), i.e. how much the field deviates from the vacuum (light axis).
    Then E=-∇Φ is radial and matches the expected "charge-like" behavior in 2D.
  • If you want, you can also visualize an O(3) analogue of Maurer–Cartan current:
        J_i = n × ∂_i n
    (this is not full SU(2) L_mu = U†∂_μU; it's the natural so(3) proxy in the S² reduction).

Dependencies: numpy, matplotlib
Run: python static_electron_vortex_3d.py
"""

import numpy as np
import matplotlib.pyplot as plt


# ----------------------------
# View controls (put FIRST, as requested)
# ----------------------------
VIEW_ELEV = 90     # elevation angle (deg)
VIEW_AZIM = -55    # azimuth angle (deg)

bloch = True

# ----------------------------
# Domain / resolution
# ----------------------------
Nx, Ny = 150, 150
Lx, Ly = 14.0, 14.0
dx, dy = Lx / Nx, Ly / Ny

x = np.linspace(-Lx/2, Lx/2, Nx, endpoint=False)
y = np.linspace(-Ly/2, Ly/2, Ny, endpoint=False)
X, Y = np.meshgrid(x, y, indexing="ij")
Z = np.zeros_like(X)


# ----------------------------
# Vortex profile (static "electron")
# ----------------------------
# n(r,phi) = (sinθ(r) cos(m phi), sinθ(r) sin(m phi), cosθ(r))
# with θ(0)=π (down) and θ(∞)=0 (vacuum up) gives a skyrmion-like texture (topological defect).
m = 1               # winding number (charge-like topological index)
core = 0.65         # core size (smaller => tighter core)
p = 2.0             # profile sharpness

r = np.sqrt(X**2 + Y**2) + 1e-12
phi = np.arctan2(Y, X)

# Smooth monotone profile: θ(0)=π, θ(∞)=0
theta = 2.0 * np.arctan((core / r) ** p)

# Field n
# Bloch
if bloch:
    gamma = np.pi / 2   # +π/2 = Bloch (тангенциальный), 0 = Néel (радиальный)
    Phi = m * phi + gamma
    nx = np.sin(theta) * np.cos(Phi)
    ny = np.sin(theta) * np.sin(Phi)
else:
# Neel
    nx = np.sin(theta) * np.cos(m * phi)
    ny = np.sin(theta) * np.sin(m * phi)
nz = np.cos(theta)

n = np.stack([nx, ny, nz], axis=2)

# Enforce unit norm (numerical safety)
n /= (np.linalg.norm(n, axis=2, keepdims=True) + 1e-12)


# ----------------------------
# Finite differences (open boundaries)
# ----------------------------
def grad_open(f):
    dfdx = np.empty_like(f)
    dfdy = np.empty_like(f)

    dfdx[1:-1, :] = (f[2:, :] - f[:-2, :]) / (2 * dx)
    dfdx[0, :] = (f[1, :] - f[0, :]) / dx
    dfdx[-1, :] = (f[-1, :] - f[-2, :]) / dx

    dfdy[:, 1:-1] = (f[:, 2:] - f[:, :-2]) / (2 * dy)
    dfdy[:, 0] = (f[:, 1] - f[:, 0]) / dy
    dfdy[:, -1] = (f[:, -1] - f[:, -2]) / dy

    return dfdx, dfdy


# ----------------------------
# Energy density (sigma-model + simple "mass" potential)
# ----------------------------
# Sigma-model static energy density: E_grad ~ (1/2) |∇n|^2
# Add a "mass" / easy-axis potential V ~ μ^2 (1 - n_z) to prefer vacuum nz=1.
kappa = 1.0
mu2 = 0.8

dnx_dx, dnx_dy = grad_open(nx)
dny_dx, dny_dy = grad_open(ny)
dnz_dx, dnz_dy = grad_open(nz)

grad2 = (dnx_dx**2 + dnx_dy**2 +
         dny_dx**2 + dny_dy**2 +
         dnz_dx**2 + dnz_dy**2)

E_grad = 0.5 * kappa * grad2
V = mu2 * (1.0 - nz)     # easy-axis penalty
E_density = E_grad + V


# ----------------------------
# "Gravitational potential" / time-dilation proxy from energy density
# ----------------------------
# In your ontology: localized energy slows local time. Here we build a smooth map:
#   gpot = -alpha * E_density   (more negative near core)
# or an omega(x)=omega0/(1+alpha E) type. For plotting, keep it simple.
alpha_g = 1.2
gpot = -alpha_g * E_density

# Normalize for display stability
gpot_disp = (gpot - np.percentile(gpot, 5)) / (np.percentile(gpot, 95) - np.percentile(gpot, 5) + 1e-12)
gpot_disp = np.clip(gpot_disp, 0.0, 1.0)


# ----------------------------
# "Electric potential" and E-field (blue)
# ----------------------------
# Reduced-model choice: scalar potential Φ proportional to deviation from vacuum along the light axis.
# Φ = (1 - n_z) is zero in vacuum and peaks at the core. Then E = -∇Φ gives a radial field.
Phi = (1.0 - nz)

dPhi_dx, dPhi_dy = grad_open(Phi)
Ex = -dPhi_dx
Ey = -dPhi_dy


# ----------------------------
# Optional: O(3) proxy of Maurer–Cartan spatial current J_i = n × ∂_i n
# ----------------------------
SHOW_MC_CURRENT = False
if SHOW_MC_CURRENT:
    dn_dx = np.stack([dnx_dx, dny_dx, dnz_dx], axis=2)
    dn_dy = np.stack([dnx_dy, dny_dy, dnz_dy], axis=2)
    Jx = np.cross(n, dn_dx)   # 3-vector at each point
    Jy = np.cross(n, dn_dy)   # 3-vector at each point
    # If you want to draw it, pick a component or a combination (e.g. along z), or draw in-plane vectors.


# ----------------------------
# Visualization settings
# ----------------------------
# Subsampling for arrows (quiver)
q_step_n = 7
q_step_E = 7

XsN = X[::q_step_n, ::q_step_n]
YsN = Y[::q_step_n, ::q_step_n]
ZsN = Z[::q_step_n, ::q_step_n]
nS = n[::q_step_n, ::q_step_n, :]

XsE = X[::q_step_E, ::q_step_E]
YsE = Y[::q_step_E, ::q_step_E]
ZsE = Z[::q_step_E, ::q_step_E]
ExS = Ex[::q_step_E, ::q_step_E]
EyS = Ey[::q_step_E, ::q_step_E]

# Scale E arrows to be readable but not insane
Emag = np.sqrt(ExS**2 + EyS**2) + 1e-12
E95 = np.percentile(Emag, 95) + 1e-12
ExD = np.clip(ExS / E95, -1.0, 1.0)
EyD = np.clip(EyS / E95, -1.0, 1.0)

LEN_N = 0.65
LEN_E = 0.75


# ----------------------------
# Plot
# ----------------------------
fig = plt.figure(figsize=(11, 8))
ax = fig.add_subplot(111, projection="3d")
ax.view_init(elev=VIEW_ELEV, azim=VIEW_AZIM)

ax.set_xlim(-Lx/2, Lx/2)
ax.set_ylim(-Ly/2, Ly/2)
ax.set_zlim(-1.1, 2.0)

ax.set_xlabel("x")
ax.set_ylabel("y")
ax.set_zlabel("z (normal)")
ax.set_title("Static electron vortex (2+1D): n-field (red), E-field (blue), grav. potential (plane)")

# Plane wireframe (light)
wf_step = 12
ax.plot_wireframe(
    X[::wf_step, ::wf_step], Y[::wf_step, ::wf_step], Z[::wf_step, ::wf_step],
    linewidth=0.5, alpha=0.20
)

# Plane colormap (gravitational potential proxy)
# User asked "gravitational potential" and suggested brown; use a brownish cmap.
cmap = plt.cm.copper
fc = cmap(gpot_disp)
fc[..., 3] = 0.55  # alpha
z_eps = 1e-3
ax.plot_surface(
    X, Y, Z + z_eps,
    facecolors=fc,
    rstride=1, cstride=1,
    shade=False,
    linewidth=0,
    antialiased=False
)

# n arrows (red)
ax.quiver(
    XsN, YsN, ZsN,
    nS[..., 0], nS[..., 1], nS[..., 2],
    length=LEN_N, normalize=True,
    color="red", linewidth=0.75
)

# E arrows (blue) in-plane
ax.quiver(
    XsE, YsE, ZsE,
    ExD, EyD, np.zeros_like(ExD),
    length=LEN_E, normalize=False,
    color="blue", linewidth=0.95
)

# Optional marker at core
ax.scatter([0.0], [0.0], [0.0], s=30, c="k")

plt.tight_layout()
plt.show()

А теперь, немного усложним задачу.

Спин и магнитный момент как внутренняя фазовая динамика

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

 Фиг. 4 «электрон со спином»
Фиг. 4 «электрон со спином»

Код скрипта под катом

Скрытый текст
"""
Spinning electron-vortex (2+1D) visualized in 3D over a plane
============================================================

What this animation shows (concept demo, consistent with our 2+1D reduction):
  • A static topological vortex n(x,y) ∈ S² ("electron") on a 2D patch, drawn as RED arrows.
  • The vortex core then starts an *internal twist / precession* (spin-analogue) and this
    generates a pseudoscalar magnetic field Bz (out-of-plane), drawn as a CYAN heatmap:
        - darker cyan  : Bz > 0
        - light cyan   : Bz < 0
  • You can switch between Néel-like (radial) and Bloch-like (tangential) vortex via BLOCH.

Important physics/ontology notes (no hand-waving):
  • In 2+1D, magnetic field is naturally a pseudoscalar Bz (single component).
  • A *global* rigid rotation Phi -> Phi + const(t) would NOT change ∇Phi and would NOT
    generate a field (pure gauge-like). To get a real “spin core”, we twist only near the core:
        Phi(x,y,t) = m*atan2(y,x) + gamma0 + delta(t)*g(r)
    where g(r) localizes the twist. This creates time-dependent spatial gradients and produces
    a nontrivial Bz from the geometric U(1) connection.

Field construction (S² toy model):
  • n = (sinθ cosPhi, sinθ sinPhi, cosθ) with θ(r) going from π at core to 0 at infinity.
  • Define a geometric U(1) connection on S²:
        A = (1 - cosθ) ∇Phi
    then
        Bz = (∇×A)_z = ∂x Ay - ∂y Ax
  This is the same "Berry/connection curvature" construction we used for the photon demo.

Dependencies: numpy, matplotlib
Run: python spinning_vortex_bz_anim.py
"""

import numpy as np
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation


# ----------------------------
# View controls FIRST (as requested)
# ----------------------------
VIEW_ELEV = 90
VIEW_AZIM = -55


# ----------------------------
# Switches / main knobs
# ----------------------------
BLOCH = True      # True: Bloch (tangential), False: Néel (radial)
SHOW_E = False    # optional: compute/draw E from a0 and A(t) (not needed for this demo)

# Vortex / core profile
m = 1               # winding (topological index)
core = 0.65         # core size
p = 2.0             # profile sharpness

# "Spin" (internal twist) parameters
OMEGA0 = 2.2        # max angular speed of the core twist (in animation time units)
t_ramp = 1.2        # how long to ramp up spin (smooth start)
r_spin = 1.4        # radius scale of the spinning core region (localization)

# Domain
Nx, Ny = 140, 140
Lx, Ly = 14.0, 14.0
dx, dy = Lx / Nx, Ly / Ny

# Animation
dt = 0.03           # animation time step (not PDE time; we're animating a prescribed texture)
nframes = 420
interval_ms = 33    # ~30 fps
repeat_anim = True

# Rendering density
q_step_n = 7
LEN_N = 0.65

# Plane rendering
wf_step = 12
B_ALPHA_MAX = 0.55
z_eps = 1e-3


# ----------------------------
# Grid
# ----------------------------
x = np.linspace(-Lx/2, Lx/2, Nx, endpoint=False)
y = np.linspace(-Ly/2, Ly/2, Ny, endpoint=False)
X, Y = np.meshgrid(x, y, indexing="ij")
Z = np.zeros_like(X)

r = np.sqrt(X**2 + Y**2) + 1e-12
phi = np.arctan2(Y, X)

# vortex helicity
gamma0 = (np.pi/2) if BLOCH else 0.0


# ----------------------------
# Finite differences (open)
# ----------------------------
def grad_open(f):
    dfdx = np.empty_like(f)
    dfdy = np.empty_like(f)

    dfdx[1:-1, :] = (f[2:, :] - f[:-2, :]) / (2*dx)
    dfdx[0, :] = (f[1, :] - f[0, :]) / dx
    dfdx[-1, :] = (f[-1, :] - f[-2, :]) / dx

    dfdy[:, 1:-1] = (f[:, 2:] - f[:, :-2]) / (2*dy)
    dfdy[:, 0] = (f[:, 1] - f[:, 0]) / dy
    dfdy[:, -1] = (f[:, -1] - f[:, -2]) / dy

    return dfdx, dfdy


# ----------------------------
# Vortex profile θ(r)
# ----------------------------
theta = 2.0 * np.arctan((core / r) ** p)
sinth = np.sin(theta)
costh = np.cos(theta)

# core localization for spin twist
g_spin = np.exp(-(r**2) / (2.0 * r_spin**2))


# ----------------------------
# Time-dependent "spin" twist delta(t)
# ----------------------------
def smooth_ramp(t, t0):
    # smooth ramp 0->1 over timescale t0
    if t <= 0:
        return 0.0
    if t >= t0:
        return 1.0
    s = t / t0
    # smoothstep
    return s*s*(3 - 2*s)

def delta_of_t(t):
    # twist angle delta(t) = ∫ Ω(t) dt; Ω ramps up smoothly to OMEGA0
    # We'll use delta(t) = OMEGA0 * t * ramp(t), which is good enough visually.
    return OMEGA0 * t * smooth_ramp(t, t_ramp)

def omega_of_t(t):
    return OMEGA0 * smooth_ramp(t, t_ramp)


# ----------------------------
# Compute n(x,y,t), A, Bz
# ----------------------------
def compute_fields(t):
    # Internal twist localized in the core
    delta = delta_of_t(t)

    # IMPORTANT: spatially localized twist => real gradients change with time
    Phi = m * phi + gamma0 + delta * g_spin

    nx = sinth * np.cos(Phi)
    ny = sinth * np.sin(Phi)
    nz = costh

    # U(1) connection A = (1 - cosθ) ∇Phi
    #dPhi_dx, dPhi_dy = grad_open(Phi)
    #fac = (1.0 - nz)
    #Ax = fac * dPhi_dx
    #Ay = fac * dPhi_dy
    # --- stable phase-derivatives: NO atan2 / NO unwrap ---
    dnx_dx, dnx_dy = grad_open(nx)
    dny_dx, dny_dy = grad_open(ny)

    den = nx*nx + ny*ny + 1e-12  # epsilon avoids 0/0 near vacuum/core
    dPhi_dx = (nx * dny_dx - ny * dnx_dx) / den
    dPhi_dy = (nx * dny_dy - ny * dnx_dy) / den

    fac = (1.0 - nz)
    Ax = fac * dPhi_dx
    Ay = fac * dPhi_dy


    # Bz = curl A (pseudoscalar in 2D)
    dAy_dx, _ = grad_open(Ay)
    _, dAx_dy = grad_open(Ax)
    Bz = dAy_dx - dAx_dy

    return nx, ny, nz, Bz


# ----------------------------
# Cyan diverging plane colors for Bz
# ----------------------------
def cyan_diverging_facecolors(Bz):
    """
    Map Bz to RGBA without relying on fragile 3D transparency sorting:
      Bz>0  -> stronger cyan
      Bz<0  -> light cyan
    """
    # robust scale to avoid flicker
    s = np.percentile(np.abs(Bz), 95) + 1e-12
    b = np.clip(Bz / s, -1.0, 1.0)

    # base RGB for positive and negative
    # pos: (0, 1, 1)   neg: (0.70, 1, 1) (lighter cyan)
    fc = np.zeros((Nx, Ny, 4), dtype=float)

    # positive
    pos = b > 0
    fc[pos, 0] = 0.00
    fc[pos, 1] = 1.00
    fc[pos, 2] = 1.00
    fc[pos, 3] = B_ALPHA_MAX * b[pos]

    # negative
    neg = b < 0
    fc[neg, 0] = 0.70
    fc[neg, 1] = 1.00
    fc[neg, 2] = 1.00
    fc[neg, 3] = B_ALPHA_MAX * (-b[neg])

    # near zero: transparent
    return fc


# ----------------------------
# Figure
# ----------------------------
fig = plt.figure(figsize=(11, 8))
ax = fig.add_subplot(111, projection="3d")


def draw_scene(t):
    nx, ny, nz, Bz = compute_fields(t)

    ax.cla()
    ax.view_init(elev=VIEW_ELEV, azim=VIEW_AZIM)
    ax.set_xlim(-Lx/2, Lx/2)
    ax.set_ylim(-Ly/2, Ly/2)
    ax.set_zlim(-1.1, 2.0)
    ax.set_xlabel("x")
    ax.set_ylabel("y")
    ax.set_zlabel("z (normal)")

    mode = "Bloch (tangential)" if BLOCH else "Néel (radial)"
    ax.set_title(
        f"Static vortex → spinning core (2+1D)   |   {mode}\n"
        f"t={t:5.2f},  Ω(t)={omega_of_t(t):.2f}   |   n: red arrows   |   Bz: cyan heatmap",
        pad=18
    )

    # plane wireframe
    ax.plot_wireframe(
        X[::wf_step, ::wf_step], Y[::wf_step, ::wf_step], Z[::wf_step, ::wf_step],
        linewidth=0.5, alpha=0.20
    )

    # Bz heatmap (single surface; avoids z-fighting artifacts)
    fc = cyan_diverging_facecolors(Bz)
    ax.plot_surface(
        X, Y, Z + z_eps,
        facecolors=fc,
        rstride=1, cstride=1,
        shade=False,
        linewidth=0,
        antialiased=False
    )

    # n arrows (subsample)
    Xs = X[::q_step_n, ::q_step_n]
    Ys = Y[::q_step_n, ::q_step_n]
    Zs = Z[::q_step_n, ::q_step_n]
    nxs = nx[::q_step_n, ::q_step_n]
    nys = ny[::q_step_n, ::q_step_n]
    nzs = nz[::q_step_n, ::q_step_n]

    ax.quiver(
        Xs, Ys, Zs,
        nxs, nys, nzs,
        length=LEN_N, normalize=True,
        color="red", linewidth=0.75
    )

    # core marker
    ax.scatter([0.0], [0.0], [0.0], s=30, c="k")

    return []


def update(frame):
    t = frame * dt
    return draw_scene(t)


anim = FuncAnimation(fig, update, frames=nframes, interval=interval_ms, blit=False, repeat=repeat_anim)
plt.tight_layout()
plt.show()

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

А что если мы попробуем подвигать электрон?

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

 Фиг. 5 «движущийся электрон со спином»
Фиг. 5 «движущийся электрон со спином»

Код анимации под катом

Скрытый текст
"""
Moving + spinning electron vortex (2+1D) in 3D isometric view
=============================================================

This is a publishable, heavily commented demo script.

We work in 2+1D (x,y,t), but visualize the phase vector field n(x,y,t) ∈ S² in 3D.

What is shown:
  • Red arrows: phase orientation n(x,y,t) (the "phase vector" on S²).
  • Blue arrows: electric-like field E(x,y,t), computed from the same geometric connection.
  • Plane color: magnetic pseudoscalar Bz (2+1D "magnetic field"), shown with sign:
        Bz > 0  -> darker blue (more positive)
        Bz < 0  -> lighter cyan (more negative)
    The plane is tinted light-brown for contrast (so “white” is not mistaken for zero).

Physics choices (consistent with our reduced model):
  • Static vortex texture:
        n = (sinθ cosΦ, sinθ sinΦ, cosθ),
        θ(r): π at the core -> 0 in vacuum (easy-axis vacuum is +z).
  • "Spin" (internal dynamics) = localized twist of phase near the core:
        Φ = m*phi + gamma0 + delta(t) g_spin(r),
    with g_spin localized around r=0.
    IMPORTANT: a global rotation Φ->Φ+const(t) is a gauge-like trivial change and would not
    generate meaningful spatial fields. We twist the CORE only.

  • Geometric U(1) connection on S² (Berry-like):
        A = (1 - cosθ) ∇Φ
        a0 = (1 - cosθ) ∂tΦ
    Then define (2+1D EM analogue):
        E = -∂t A - ∇a0
        Bz_spin = (∇×A)_z

  • Motion contribution (kinematic mixing, for visualization / sanity):
        Bz_move ≈ (v × E)_z = vx*Ey - vy*Ex
    In this demo we superpose:
        Bz_total = B_spin_gain * Bz_spin + B_move_gain * Bz_move

Switches:
  • BLOCH = True/False (Bloch tangential vs Néel radial vortex).
  • SHOW_B_SPIN / SHOW_B_MOVE
  • Gains B_spin_gain / B_move_gain

Dependencies:
  numpy, matplotlib

Run:
  python moving_spinning_vortex_3d_full.py
"""

import numpy as np
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation


# =============================================================================
# 0) VIEW CONTROLS (FIRST, as requested)
# =============================================================================
VIEW_ELEV = 90
VIEW_AZIM = -55


# =============================================================================
# 1) MAIN SWITCHES / PARAMS (put here so users can "poke" the model)
# =============================================================================
BLOCH = True            # True: Bloch (tangential), False: Néel (radial)

# Show which contributions to B we include
SHOW_B_SPIN = True
SHOW_B_MOVE = True

# Relative weights for the two contributions
B_spin_gain = 1.0
B_move_gain = 1.0

# Base plane tint (light-brown) so that "white" does not look like background truth
BASE_PLANE_COLOR = (0.80, 0.68, 0.55, 0.70)  # RGBA

# Vortex parameters
m = 1
core = 0.65
p = 2.0

# Internal twist ("spin") parameters
OMEGA0 = 2.0       # max core twist angular speed (animation time units)
t_ramp = 1.2       # smooth start duration
r_spin = 1.4       # core twist localization radius

# Translation path (left -> right)
Lx, Ly = 14.0, 14.0
x0_start = -0.75 * Lx
x0_end   = +0.75 * Lx
y0 = 0.0

# Grid
Nx, Ny = 160, 160
dx, dy = Lx / Nx, Ly / Ny

# Animation
dt = 0.03
frames = 420
interval_ms = 33
repeat_anim = True

# Drawing density
q_step_n = 7
q_step_E = 10
LEN_N = 0.65
LEN_E = 0.80

# Plane wireframe and overlay tuning
wf_step = 12
z_eps = 1e-3
E_clip_pct = 95
B_clip_pct = 95
B_ALPHA_MAX = 0.85


# =============================================================================
# 2) GRID
# =============================================================================
x = np.linspace(-Lx/2, Lx/2, Nx, endpoint=False)
y = np.linspace(-Ly/2, Ly/2, Ny, endpoint=False)
X, Y = np.meshgrid(x, y, indexing="ij")
Z0 = np.zeros_like(X)

# Subsample coordinates for quivers
XsN = X[::q_step_n, ::q_step_n]
YsN = Y[::q_step_n, ::q_step_n]
ZsN = Z0[::q_step_n, ::q_step_n]

XsE = X[::q_step_E, ::q_step_E]
YsE = Y[::q_step_E, ::q_step_E]
ZsE = Z0[::q_step_E, ::q_step_E]


# =============================================================================
# 3) NUMERICAL UTILITIES
# =============================================================================
def smoothstep(s: float) -> float:
    s = np.clip(s, 0.0, 1.0)
    return s*s*(3 - 2*s)

def ramp01(t: float, t0: float) -> float:
    """Smooth ramp 0->1 during [0, t0]."""
    if t <= 0:
        return 0.0
    if t >= t0:
        return 1.0
    s = t / t0
    return s*s*(3 - 2*s)

def omega_of_t(t: float) -> float:
    return OMEGA0 * ramp01(t, t_ramp)

def delta_of_t(t: float) -> float:
    # For visualization: delta(t) ≈ ∫Ω(t)dt ~ Ω0 * t * ramp(t)
    return OMEGA0 * t * ramp01(t, t_ramp)

def x0_of_t(t: float) -> float:
    """Move x0 from start to end smoothly across the full animation duration."""
    Ttot = frames * dt
    s = smoothstep(t / Ttot)
    return x0_start + (x0_end - x0_start) * s

def grad_open(f: np.ndarray):
    """Open-boundary finite differences (no periodic wrap)."""
    dfdx = np.empty_like(f)
    dfdy = np.empty_like(f)

    dfdx[1:-1, :] = (f[2:, :] - f[:-2, :]) / (2*dx)
    dfdx[0, :]    = (f[1, :] - f[0, :]) / dx
    dfdx[-1, :]   = (f[-1, :] - f[-2, :]) / dx

    dfdy[:, 1:-1] = (f[:, 2:] - f[:, :-2]) / (2*dy)
    dfdy[:, 0]    = (f[:, 1] - f[:, 0]) / dy
    dfdy[:, -1]   = (f[:, -1] - f[:, -2]) / dy

    return dfdx, dfdy


# =============================================================================
# 4) VORTEX FIELD n(x,y,t)
# =============================================================================
gamma0 = (np.pi/2) if BLOCH else 0.0

def build_n_fields(t: float):
    """
    Construct n(x,y,t) for a moving vortex with localized core twist.
    Returns (nx, ny, nz, theta, g_spin).
    """
    x0 = x0_of_t(t)

    # Coordinates in the vortex frame
    Xr = X - x0
    Yr = Y - y0
    r = np.sqrt(Xr*Xr + Yr*Yr) + 1e-12
    phi = np.arctan2(Yr, Xr)

    # Radial profile θ(r): π at core -> 0 at infinity
    theta = 2.0 * np.arctan((core / r) ** p)
    sinth = np.sin(theta)
    costh = np.cos(theta)

    # Localized twist (spin analogue)
    g_spin = np.exp(-(r*r) / (2.0 * r_spin*r_spin))
    Phi = m * phi + gamma0 + delta_of_t(t) * g_spin

    # n on S²
    nx = sinth * np.cos(Phi)
    ny = sinth * np.sin(Phi)
    nz = costh

    # Normalize (safety)
    inv = 1.0 / (np.sqrt(nx*nx + ny*ny + nz*nz) + 1e-12)
    nx *= inv; ny *= inv; nz *= inv

    return nx, ny, nz, theta, g_spin


# =============================================================================
# 5) GEOMETRIC CONNECTION -> A, a0, E, Bz
# =============================================================================
def phase_derivatives_from_n(nx, ny):
    """
    Stable derivatives of phase Φ without atan2 unwrap / branch cuts.

    ∂i Φ = (nx ∂i ny - ny ∂i nx)/(nx^2+ny^2)
    """
    dnx_dx, dnx_dy = grad_open(nx)
    dny_dx, dny_dy = grad_open(ny)

    den = nx*nx + ny*ny + 1e-12
    dPhi_dx = (nx * dny_dx - ny * dnx_dx) / den
    dPhi_dy = (nx * dny_dy - ny * dnx_dy) / den
    return dPhi_dx, dPhi_dy

def phi_time_derivative_from_n(nx, ny, nx_prev, ny_prev):
    """
    Stable time derivative ∂tΦ via:
      ∂t Φ ≈ (nx ∂t ny - ny ∂t nx)/(nx^2+ny^2)
    """
    den = nx*nx + ny*ny + 1e-12
    dnx_dt = (nx - nx_prev) / dt
    dny_dt = (ny - ny_prev) / dt
    dPhi_dt = (nx * dny_dt - ny * dnx_dt) / den
    return dPhi_dt

def compute_fields_from_connection(
    t, nx, ny, nz,
    nx_prev, ny_prev, nz_prev
):
    """
    Compute:
      A = (1-cosθ)∇Φ with (1-cosθ)=(1-nz)
      a0 = (1-nz)∂tΦ
      E = -∂tA - ∇a0
      Bz_spin = curl A
    """
    # Spatial derivatives of Φ (stable, no atan2)
    dPhi_dx, dPhi_dy = phase_derivatives_from_n(nx, ny)

    fac = (1.0 - nz)
    Ax = fac * dPhi_dx
    Ay = fac * dPhi_dy

    # Spin magnetic field: curl A
    dAy_dx, _ = grad_open(Ay)
    _, dAx_dy = grad_open(Ax)
    Bz_spin = dAy_dx - dAx_dy

    # Temporal derivative ∂tΦ (stable)
    dPhi_dt = phi_time_derivative_from_n(nx, ny, nx_prev, ny_prev)
    a0 = fac * dPhi_dt

    # Build A at previous frame for ∂tA
    dPhi_p_dx, dPhi_p_dy = phase_derivatives_from_n(nx_prev, ny_prev)
    fac_p = (1.0 - nz_prev)
    Ax_p = fac_p * dPhi_p_dx
    Ay_p = fac_p * dPhi_p_dy

    dAx_dt = (Ax - Ax_p) / dt
    dAy_dt = (Ay - Ay_p) / dt

    da0_dx, da0_dy = grad_open(a0)

    Ex = -dAx_dt - da0_dx
    Ey = -dAy_dt - da0_dy

    return Ex, Ey, Bz_spin


# =============================================================================
# 6) COLORING: asymmetric Bz overlay on tinted plane
# =============================================================================
def bz_facecolors_asymmetric(B, base_rgba, clip_pct=95, alpha_max=0.85):
    """
    Plane facecolors where sign of B is encoded by two different "blue" ramps:

      B > 0 : white -> darker blue
      B < 0 : white -> light cyan

    Base plane is tinted (brown) to avoid "white means background".

    Note: In matplotlib 3D, per-face alpha is imperfect; we keep it simple but readable.
    """
    # Robust scale avoids flicker
    s = np.percentile(np.abs(B), clip_pct) + 1e-12
    b = np.clip(B / s, -1.0, 1.0)

    fc = np.zeros((Nx, Ny, 4), dtype=float)
    fc[..., 0] = base_rgba[0]
    fc[..., 1] = base_rgba[1]
    fc[..., 2] = base_rgba[2]
    fc[..., 3] = base_rgba[3]

    a = alpha_max * np.abs(b)

    # Positive ramp -> darker blue
    pos = b > 0
    if np.any(pos):
        target = np.array([0.00, 0.50, 0.95])  # dark-ish blue
        white  = np.array([1.00, 1.00, 1.00])
        w = np.abs(b[pos])
        # need proper broadcasting
        rgb = white*(1 - w[..., None]) + target*(w[..., None])
        fc[pos, 0] = rgb[..., 0]
        fc[pos, 1] = rgb[..., 1]
        fc[pos, 2] = rgb[..., 2]
        fc[pos, 3] = np.maximum(fc[pos, 3], a[pos])

    # Negative ramp -> light cyan
    neg = b < 0
    if np.any(neg):
        target = np.array([0.55, 0.90, 1.00])  # light cyan
        white  = np.array([1.00, 1.00, 1.00])
        w = np.abs(b[neg])
        rgb = white*(1 - w[..., None]) + target*(w[..., None])
        fc[neg, 0] = rgb[..., 0]
        fc[neg, 1] = rgb[..., 1]
        fc[neg, 2] = rgb[..., 2]
        fc[neg, 3] = np.maximum(fc[neg, 3], a[neg])

    return fc


# =============================================================================
# 7) ANIMATION SETUP
# =============================================================================
fig = plt.figure(figsize=(11, 8))
ax = fig.add_subplot(111, projection="3d")

# Seed previous fields for time-derivatives
t_init = 0.0
nx_prev, ny_prev, nz_prev, _, _ = build_n_fields(t_init)


def draw_frame(t: float):
    global nx_prev, ny_prev, nz_prev

    # Current n-field
    nx, ny, nz, _, _ = build_n_fields(t)

    # Fields from connection
    Ex, Ey, Bz_spin = compute_fields_from_connection(
        t, nx, ny, nz,
        nx_prev, ny_prev, nz_prev
    )

    # Update prev for next frame
    nx_prev, ny_prev, nz_prev = nx, ny, nz

    # "Motion" magnetic contribution: B_move ≈ (v × E)_z
    # Estimate instantaneous velocity vx from x0(t)
    x_now = x0_of_t(t)
    x_prev = x0_of_t(max(t - dt, 0.0))
    vx = (x_now - x_prev) / dt
    vy = 0.0
    Bz_move = vx * Ey - vy * Ex

    # Superposition
    B_total = np.zeros_like(Bz_spin)
    if SHOW_B_SPIN:
        B_total += B_spin_gain * Bz_spin
    if SHOW_B_MOVE:
        B_total += B_move_gain * Bz_move

    # ---- PLOT ----
    ax.cla()
    ax.view_init(elev=VIEW_ELEV, azim=VIEW_AZIM)
    ax.set_xlim(-Lx/2, Lx/2)
    ax.set_ylim(-Ly/2, Ly/2)
    ax.set_zlim(-1.1, 2.0)
    ax.set_xlabel("x")
    ax.set_ylabel("y")
    ax.set_zlabel("z")

    mode = "Bloch" if BLOCH else "Néel"

    title_lines = [
        f"Moving + spinning vortex (2+1D) | {mode}",
        f"t={t:5.2f}   x0(t)={x_now:+.2f}   vx≈{vx:+.2f}   Ω(t)={omega_of_t(t):.2f}",
        f"B = {('spin' if SHOW_B_SPIN else '')}{(' + ' if (SHOW_B_SPIN and SHOW_B_MOVE) else '')}{('move' if SHOW_B_MOVE else '')} "
        f"(gains: {B_spin_gain:.2f}, {B_move_gain:.2f})",
        "n: red arrows   E: blue arrows   Bz (pos/neg): plane colors"
    ]
    ax.set_title("\n".join(title_lines), pad=18)

    # Plane wireframe
    ax.plot_wireframe(
        X[::wf_step, ::wf_step],
        Y[::wf_step, ::wf_step],
        Z0[::wf_step, ::wf_step],
        linewidth=0.5, alpha=0.20
    )

    # Bz overlay (asymmetric blue ramps on brown base)
    fc = bz_facecolors_asymmetric(B_total, BASE_PLANE_COLOR, clip_pct=B_clip_pct, alpha_max=B_ALPHA_MAX)
    ax.plot_surface(
        X, Y, Z0 + z_eps,
        facecolors=fc,
        rstride=1, cstride=1,
        shade=False,
        linewidth=0,
        antialiased=False
    )

    # Red arrows: n
    nxs = nx[::q_step_n, ::q_step_n]
    nys = ny[::q_step_n, ::q_step_n]
    nzs = nz[::q_step_n, ::q_step_n]
    ax.quiver(
        XsN, YsN, ZsN,
        nxs, nys, nzs,
        length=LEN_N, normalize=True,
        color="red", linewidth=0.75
    )

    # Blue arrows: E (in-plane), clipped for readability
    ExS = Ex[::q_step_E, ::q_step_E]
    EyS = Ey[::q_step_E, ::q_step_E]
    Em = np.sqrt(ExS*ExS + EyS*EyS) + 1e-12
    sE = np.percentile(Em, E_clip_pct) + 1e-12
    ExD = np.clip(ExS / sE, -1.0, 1.0)
    EyD = np.clip(EyS / sE, -1.0, 1.0)

    ax.quiver(
        XsE, YsE, ZsE,
        ExD, EyD, np.zeros_like(ExD),
        length=LEN_E, normalize=False,
        color="blue", linewidth=0.95, alpha=0.95
    )

    # Core marker
    ax.scatter([x_now], [0.0], [0.0], s=35, c="k")

    return []


def update(frame: int):
    t = frame * dt
    return draw_frame(t)


anim = FuncAnimation(
    fig, update,
    frames=frames,
    interval=interval_ms,
    blit=False,
    repeat=repeat_anim
)

plt.tight_layout()
plt.show()

Таким образом, различные проявления фазовой динамики, которые являются тем, что мы привыкли измерять, как гравитационное, электрическое, магнитное поля — в данной модели являются следствием динамики единственной сущности. Фазовые вихри действительно будут взаимодействовать точно так же, как мы привыкли это наблюдать. Однако, заметьте — поскольку все поля являются порождением динамической фазовой структуры (производными проекциями), то эти поля не влияют на сам электрон. Помните, у Ландау — проблема самодействия движущегося электрона и искусственное ограничение на то, чтобы собственные поля выкидывать из расчётов? Здесь нет необходимости в таком ограничении.

А если мы его подвигаем ну очень быстро?

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

 Фиг. 6 «движущийся электрон со спином. Релятивизм»
Фиг. 6 «движущийся электрон со спином. Релятивизм»

Код анимации под катом

Скрытый текст
"""
Moving + spinning vortex with Lorentz contraction & time dilation (2+1D) in 3D view
==================================================================================

We are in a reduced 2+1D demo: space is (x,y), field is n(x,y,t) ∈ S².

We show:
  • Red arrows: n(x,y,t)
  • Blue arrows: E(x,y,t) from the geometric connection
  • Plane colors: Bz_total = B_spin + B_move with sign-coded blue ramps on a brown plane

NEW in this version:
  • Lorentz-like kinematics for a moving vortex:
      - length contraction along the direction of motion (x)
      - time dilation for internal rotation ("spin rate")

In a strict 3+1D theory this would be derived; here we implement the *conceptual mechanism*:
  - Spatial contraction: replace r² = (x-x0)² + (y-y0)² by
        r² = (γ (x-x0))² + (y-y0)²
  - Time dilation for internal twist: use proper time τ:
        dτ = dt / γ
        delta(t) = ∫ Ω(τ) dτ  => effectively Ω_lab = Ω0 / γ

Switches:
  LORENTZ = True/False
  BLOCH = True/False
  SHOW_B_SPIN / SHOW_B_MOVE
  Gains B_spin_gain / B_move_gain

Dependencies: numpy, matplotlib
"""

import numpy as np
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation


# =============================================================================
# 0) VIEW CONTROLS (FIRST)
# =============================================================================
VIEW_ELEV = 90
VIEW_AZIM = -55


# =============================================================================
# 1) MAIN SWITCHES / PARAMS
# =============================================================================
BLOCH = True

# NEW: Lorentz kinematics switches
LORENTZ = True
C_LIGHT = 1.0        # "c" in our units; keep 1.0

# Show which contributions to B we include
SHOW_B_SPIN = True
SHOW_B_MOVE = True

# Relative weights for the two contributions
B_spin_gain = 1.0
B_move_gain = 1.0

# Base plane tint (light-brown) so white isn't "background truth"
BASE_PLANE_COLOR = (0.80, 0.68, 0.55, 0.70)  # RGBA

# Vortex parameters
m = 1
core = 0.65
p = 2.0

# Internal twist ("spin") parameters
OMEGA0 = 2.0       # max internal angular speed in proper time (conceptually)
t_ramp = 1.2       # smooth start duration
r_spin = 1.4       # localization radius for core twist

# Translation path (left -> right)
Lx, Ly = 14.0, 14.0

#x0_start = -0.75 * Lx
x0_start = 0
#x0_end   = +0.75 * Lx
x0_end   = +Lx/2 * Lx
y0 = 0.0

vx_max = 0.6   # желаемый максимум (<1)
# Почти классика - 0.3
# Видимое Лоренц-сжатие 0.5
# Ультрарелятивистский “блин” 0.8–0.9

# Grid
Nx, Ny = 160, 160
#Nx, Ny = 220, 220
dx, dy = Lx / Nx, Ly / Ny

# Animation
dt = 0.03
frames = 420
interval_ms = 33
repeat_anim = True

# Drawing density
q_step_n = 7
q_step_E = 10
LEN_N = 0.65
LEN_E = 0.80

# Plane wireframe and overlay tuning
wf_step = 12
z_eps = 1e-3
E_clip_pct = 95
B_clip_pct = 95
B_ALPHA_MAX = 0.85


# =============================================================================
# 2) GRID
# =============================================================================
x = np.linspace(-Lx/2, Lx/2, Nx, endpoint=False)
y = np.linspace(-Ly/2, Ly/2, Ny, endpoint=False)
X, Y = np.meshgrid(x, y, indexing="ij")
Z0 = np.zeros_like(X)

# Subsample coords for quivers
XsN = X[::q_step_n, ::q_step_n]
YsN = Y[::q_step_n, ::q_step_n]
ZsN = Z0[::q_step_n, ::q_step_n]

XsE = X[::q_step_E, ::q_step_E]
YsE = Y[::q_step_E, ::q_step_E]
ZsE = Z0[::q_step_E, ::q_step_E]


# =============================================================================
# 3) NUMERICAL UTILITIES
# =============================================================================
def smoothstep(s: float) -> float:
    s = np.clip(s, 0.0, 1.0)
    return s*s*(3 - 2*s)

def ramp01(t: float, t0: float) -> float:
    if t <= 0:
        return 0.0
    if t >= t0:
        return 1.0
    s = t / t0
    return s*s*(3 - 2*s)

#def x0_of_t(t: float) -> float:
#    Ttot = frames * dt
#    s = smoothstep(t / Ttot)
#    return x0_start + (x0_end - x0_start) * s

def x0_of_t(t):
    Ttot = frames * dt
    s = smoothstep(t / Ttot)
    return x0_start + vx_max * Ttot * s


def grad_open(f: np.ndarray):
    dfdx = np.empty_like(f)
    dfdy = np.empty_like(f)

    dfdx[1:-1, :] = (f[2:, :] - f[:-2, :]) / (2*dx)
    dfdx[0, :]    = (f[1, :] - f[0, :]) / dx
    dfdx[-1, :]   = (f[-1, :] - f[-2, :]) / dx

    dfdy[:, 1:-1] = (f[:, 2:] - f[:, :-2]) / (2*dy)
    dfdy[:, 0]    = (f[:, 1] - f[:, 0]) / dy
    dfdy[:, -1]   = (f[:, -1] - f[:, -2]) / dy

    return dfdx, dfdy

def safe_gamma(vx: float, vy: float = 0.0):
    v2 = vx*vx + vy*vy
    # clamp to avoid blow-up if user puts vx>=c
    v2 = min(v2, 0.9999 * C_LIGHT*C_LIGHT)
    return 1.0 / np.sqrt(1.0 - v2/(C_LIGHT*C_LIGHT))


# =============================================================================
# 4) TIME: proper time accumulator for time dilation
# =============================================================================
# We integrate proper time τ(t) = ∫ dt / γ(t)
tau = 0.0

def omega_proper(t: float) -> float:
    # internal spin speed defined in *proper time*
    return OMEGA0 * ramp01(t, t_ramp)


# =============================================================================
# 5) VORTEX FIELD n(x,y,t)
# =============================================================================
gamma0 = (np.pi/2) if BLOCH else 0.0

def build_n_fields(t: float, vx: float, vy: float, delta_angle: float):
    """
    Construct n(x,y,t) for a moving vortex with localized core twist.
    If LORENTZ is True, apply length contraction along x by γ.

    delta_angle is the internal twist phase (already time-dilated via τ).
    """
    x0 = x0_of_t(t)

    # coords relative to moving core
    Xr = X - x0
    Yr = Y - y0

    # Lorentz contraction along direction of motion (x)
    if LORENTZ:
        gam = safe_gamma(vx, vy)
        Xr_eff = gam * Xr     # contracted shape in lab frame => field varies faster in x
    else:
        gam = 1.0
        Xr_eff = Xr

    r = np.sqrt(Xr_eff*Xr_eff + Yr*Yr) + 1e-12
    phi = np.arctan2(Yr, Xr_eff)   # angle in the deformed core coordinates

    # radial profile θ(r): π at core -> 0 at infinity
    theta = 2.0 * np.arctan((core / r) ** p)
    sinth = np.sin(theta)
    costh = np.cos(theta)

    # localized twist profile
    g_spin = np.exp(-(r*r) / (2.0 * r_spin*r_spin))

    # Phase
    Phi = m * phi + gamma0 + delta_angle * g_spin

    nx = sinth * np.cos(Phi)
    ny = sinth * np.sin(Phi)
    nz = costh

    inv = 1.0 / (np.sqrt(nx*nx + ny*ny + nz*nz) + 1e-12)
    nx *= inv; ny *= inv; nz *= inv

    return nx, ny, nz


# =============================================================================
# 6) GEOMETRIC CONNECTION -> E, B
# =============================================================================
def phase_derivatives_from_n(nx, ny):
    """∂iΦ = (nx ∂i ny - ny ∂i nx)/(nx^2+ny^2) (stable, no atan2 unwrap)."""
    dnx_dx, dnx_dy = grad_open(nx)
    dny_dx, dny_dy = grad_open(ny)
    den = nx*nx + ny*ny + 1e-12
    dPhi_dx = (nx * dny_dx - ny * dnx_dx) / den
    dPhi_dy = (nx * dny_dy - ny * dnx_dy) / den
    return dPhi_dx, dPhi_dy

def phi_time_derivative_from_n(nx, ny, nx_prev, ny_prev):
    """∂tΦ ≈ (nx ∂t ny - ny ∂t nx)/(nx^2+ny^2)."""
    den = nx*nx + ny*ny + 1e-12
    dnx_dt = (nx - nx_prev) / dt
    dny_dt = (ny - ny_prev) / dt
    return (nx * dny_dt - ny * dnx_dt) / den

def compute_fields_from_connection(nx, ny, nz, nx_prev, ny_prev, nz_prev):
    """
    A = (1-nz) ∇Φ
    a0 = (1-nz) ∂tΦ
    E = -∂tA - ∇a0
    Bz_spin = curl A
    """
    dPhi_dx, dPhi_dy = phase_derivatives_from_n(nx, ny)
    fac = (1.0 - nz)
    Ax = fac * dPhi_dx
    Ay = fac * dPhi_dy

    dAy_dx, _ = grad_open(Ay)
    _, dAx_dy = grad_open(Ax)
    Bz_spin = dAy_dx - dAx_dy

    dPhi_dt = phi_time_derivative_from_n(nx, ny, nx_prev, ny_prev)
    a0 = fac * dPhi_dt
    da0_dx, da0_dy = grad_open(a0)

    # A_prev for ∂tA
    dPhi_p_dx, dPhi_p_dy = phase_derivatives_from_n(nx_prev, ny_prev)
    fac_p = (1.0 - nz_prev)
    Ax_p = fac_p * dPhi_p_dx
    Ay_p = fac_p * dPhi_p_dy
    dAx_dt = (Ax - Ax_p) / dt
    dAy_dt = (Ay - Ay_p) / dt

    Ex = -dAx_dt - da0_dx
    Ey = -dAy_dt - da0_dy

    return Ex, Ey, Bz_spin


# =============================================================================
# 7) COLORING: asymmetric Bz overlay on tinted plane
# =============================================================================
def bz_facecolors_asymmetric(B, base_rgba, clip_pct=95, alpha_max=0.85):
    s = np.percentile(np.abs(B), clip_pct) + 1e-12
    b = np.clip(B / s, -1.0, 1.0)

    fc = np.zeros((Nx, Ny, 4), dtype=float)
    fc[..., 0] = base_rgba[0]
    fc[..., 1] = base_rgba[1]
    fc[..., 2] = base_rgba[2]
    fc[..., 3] = base_rgba[3]

    a = alpha_max * np.abs(b)

    # positive: white -> darker blue
    pos = b > 0
    if np.any(pos):
        target = np.array([0.00, 0.50, 0.95])
        white  = np.array([1.00, 1.00, 1.00])
        w = np.abs(b[pos])
        rgb = white*(1 - w[..., None]) + target*(w[..., None])
        fc[pos, 0] = rgb[..., 0]
        fc[pos, 1] = rgb[..., 1]
        fc[pos, 2] = rgb[..., 2]
        fc[pos, 3] = np.maximum(fc[pos, 3], a[pos])

    # negative: white -> light cyan
    neg = b < 0
    if np.any(neg):
        target = np.array([0.55, 0.90, 1.00])
        white  = np.array([1.00, 1.00, 1.00])
        w = np.abs(b[neg])
        rgb = white*(1 - w[..., None]) + target*(w[..., None])
        fc[neg, 0] = rgb[..., 0]
        fc[neg, 1] = rgb[..., 1]
        fc[neg, 2] = rgb[..., 2]
        fc[neg, 3] = np.maximum(fc[neg, 3], a[neg])

    return fc


# =============================================================================
# 8) ANIMATION
# =============================================================================
fig = plt.figure(figsize=(11, 8))
ax = fig.add_subplot(111, projection="3d")

# Seed previous fields
t_init = 0.0
# initial vx estimate
vx_init = (x0_of_t(dt) - x0_of_t(0.0)) / dt
delta_init = 0.0
nx_prev, ny_prev, nz_prev = build_n_fields(t_init, vx_init, 0.0, delta_init)

def update(frame: int):
    global nx_prev, ny_prev, nz_prev, tau

    t = frame * dt

    # velocity estimate from x0(t)
    x_now = x0_of_t(t)
    x_prev = x0_of_t(max(t - dt, 0.0))
    vx = (x_now - x_prev) / dt
    vy = 0.0

    gam = safe_gamma(vx, vy)

    # Proper time integration for time dilation: dτ = dt/γ
    if LORENTZ:
        tau += dt / gam
        delta_angle = omega_proper(tau) * tau   # rough but monotone; good for demo
    else:
        tau += dt
        delta_angle = omega_proper(t) * t

    # Build field
    nx, ny, nz = build_n_fields(t, vx, vy, delta_angle)

    # Fields from connection
    Ex, Ey, Bz_spin = compute_fields_from_connection(nx, ny, nz, nx_prev, ny_prev, nz_prev)

    # Update prev
    nx_prev, ny_prev, nz_prev = nx, ny, nz

    # Motion contribution: B_move ≈ (v × E)_z
    Bz_move = vx * Ey - vy * Ex

    # Superposed magnetic field
    B_total = np.zeros_like(Bz_spin)
    if SHOW_B_SPIN:
        B_total += B_spin_gain * Bz_spin
    if SHOW_B_MOVE:
        B_total += B_move_gain * Bz_move

    # ---- Draw ----
    ax.cla()
    ax.view_init(elev=VIEW_ELEV, azim=VIEW_AZIM)
    ax.set_xlim(-Lx/2, Lx/2)
    ax.set_ylim(-Ly/2, Ly/2)
    ax.set_zlim(-1.1, 2.0)
    ax.set_xlabel("x")
    ax.set_ylabel("y")
    ax.set_zlabel("z")

    mode = "Bloch" if BLOCH else "Néel"
    lor = "ON" if LORENTZ else "OFF"
    ax.set_title(
        f"Moving + spinning vortex (2+1D) | {mode} | Lorentz={lor}\n"
        f"t={t:5.2f}  τ={tau:5.2f}  x0={x_now:+.2f}  vx≈{vx:+.2f}  γ≈{gam:.3f}  Ω_lab≈{omega_proper(tau)/gam if LORENTZ else omega_proper(t):.2f}\n"
        f"n: red   E: blue   Bz: plane colors (B = spin + move)",
        pad=18
    )

    # Wireframe
    ax.plot_wireframe(
        X[::wf_step, ::wf_step], Y[::wf_step, ::wf_step], Z0[::wf_step, ::wf_step],
        linewidth=0.5, alpha=0.20
    )

    # B overlay
    fc = bz_facecolors_asymmetric(B_total, BASE_PLANE_COLOR, clip_pct=B_clip_pct, alpha_max=B_ALPHA_MAX)
    ax.plot_surface(
        X, Y, Z0 + z_eps,
        facecolors=fc,
        rstride=1, cstride=1,
        shade=False,
        linewidth=0,
        antialiased=False
    )

    # n arrows (red)
    nxs = nx[::q_step_n, ::q_step_n]
    nys = ny[::q_step_n, ::q_step_n]
    nzs = nz[::q_step_n, ::q_step_n]
    ax.quiver(
        XsN, YsN, ZsN,
        nxs, nys, nzs,
        length=LEN_N, normalize=True,
        color="red", linewidth=0.75
    )

    # E arrows (blue), clipped
    ExS = Ex[::q_step_E, ::q_step_E]
    EyS = Ey[::q_step_E, ::q_step_E]
    Em = np.sqrt(ExS*ExS + EyS*EyS) + 1e-12
    sE = np.percentile(Em, E_clip_pct) + 1e-12
    ExD = np.clip(ExS / sE, -1.0, 1.0)
    EyD = np.clip(EyS / sE, -1.0, 1.0)

    ax.quiver(
        XsE, YsE, ZsE,
        ExD, EyD, np.zeros_like(ExD),
        length=LEN_E, normalize=False,
        color="blue", linewidth=0.95, alpha=0.95
    )

    # core marker
    ax.scatter([x_now], [0.0], [0.0], s=35, c="k")

    return []


anim = FuncAnimation(fig, update, frames=frames, interval=interval_ms, blit=False, repeat=repeat_anim)
plt.tight_layout()
plt.show()

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

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

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

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

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

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

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

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

Промежуточный итог

Я очень надеюсь на то, что мне удалось донести основную идею, онтологию теории. Здесь, разумеется, нужно подключать интуицию. Экстраполяция на пространство 3+1D — достаточно заковыриста, но когда Вам это удастся, думаю, вы поразитесь красоте возникающей картины!

Мне хотелось бы услышать — насколько интересна такая подача материала? Что следует исправить или расширить?

Кроме того, буду рад любой помощи с визуализацией (предпочтительно в динамике) этой модели.