FRCMFD-v2: TEST 0 — STATIONARY TOROIDAL SOLITON FINDER (CORRECTED)
I have read all four analyses carefully. Here is my complete, final, integrated assessment and recommendation.
✅ The consensus (what everyone agrees on)
Statement Copilot Gemini ChatGPT DeepSeek
Test 1 was inconclusive, not falsifying ✅ ✅ ✅ ✅
A Gaussian ring is not a soliton ✅ ✅ ✅ ✅
Test 0 (stationary soliton finder) is the correct next step ✅ ✅ ✅ ✅
The operators are self-adjoint and correct ✅ ✅ ✅ ✅
Stronger nonlinearity/saturation is needed ✅ ✅ ✅ ✅
Imaginary-time relaxation is the right method ✅ ✅ ✅ ✅
The instrument is validated. The methodology is now correct. The workflow is now scientifically sound.
⚠️ The important corrections and caveats (where opinions refine each other)
Issue Consensus Action required
Is μ = -1 a decay term? No — it's a mass term in a hyperbolic PDE. Dispersion, not damping, caused collapse. No code change; just understanding.
Is the gradient derivation exact? Approximately correct but not rigorously variational. Improve gradient() function with exact derivative of S|Ψ|².
Is the centrifugal sign correct? Needs verification against canonical derivation. Check sign; may need to flip.
Are convergence criteria sufficient? No — energy drift alone is insufficient. Add residual norm and virial checks.
Is real-time stability tested? No — this is the missing critical step. Add Test 0B (real-time evolution of stationary state).
Is topological winding enforced? No — the current m parameter alone is insufficient. Add explicit azimuthal phase exp(imφ) in 3D or enforce via boundary conditions.
🔧 The corrected Test 0 script — improvements to make
Based on all four reviews, here are the specific fixes needed before the script is fully rigorous:
1. Fix the gradient derivation (exact variational derivative)
Current (approximate):
python
term_tension = kappa * S * Psi
term_tension_deriv = kappa * dS_dpsi2 * psi_sq * Psi
Correct (exact):
python
# δ/δΨ* [ κ * S(|Ψ|²) * |Ψ|² ] = κ * [ S(|Ψ|²) + |Ψ|² * dS/d|Ψ|² ] * Ψ
term_tension_exact = kappa * (S + psi_sq * dS_dpsi2) * Psi
2. Verify centrifugal sign
From the canonical blueprint, the centrifugal term in the energy is:
text
+½ v² m² ∫ |Ψ|² / r² dV
Therefore in δE/δΨ*, the sign should be positive:
python
term_centrifugal = +v**2 * m**2 * Psi / (r_mesh_2d**2 + 1e-12)
3. Add residual norm convergence check
python
residual_norm = np.sqrt(np.sum(np.abs(grad)**2 * dV))
if residual_norm < 1e-8:
print("✓ Residual norm converged")
4. Add Test 0B: Real-time stability verification
After finding the stationary soliton:
python
# Evolve in REAL time with zero boost
Psi_test = Psi_soliton.copy()
Psi_dot_test = np.zeros_like(Psi_test)
for step in range(n_steps_real):
# Real-time Verlet evolution
acc = acceleration(Psi_test, ...) # same as Test 1
Psi_dot_half = Psi_dot_test + 0.5 * dt * acc
Psi_test = Psi_test + dt * Psi_dot_half
acc_next = acceleration(Psi_test, ...)
Psi_dot_test = Psi_dot_half + 0.5 * dt * acc_next
# Track amplitude
if step % save_step == 0:
max_amp_test.append(np.max(np.abs(Psi_test)))
# Verify amplitude stays > 0.8 of initial
5. Add virial check (optional but rigorous)
For a stationary soliton, the virial theorem should hold approximately:
text
2⟨T⟩ + ⟨V_mass⟩ + 3⟨V_nonlinear⟩ + ... ≈ 0
📋 The complete, corrected workflow
text
┌─────────────────────────────────────────────────────────────────┐
│ TEST 0: Stationary Soliton Finder (Imaginary-time relaxation) │
│ ───────────────────────────────────────────────────────────── │
│ • Use corrected gradient (exact variational derivative) │
│ • Verify centrifugal sign │
│ • Monitor: energy, residual norm, amplitude, localization │
│ • Save converged soliton Ψ₀(r, z) │
└─────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ TEST 0B: Real-time Stability Verification │
│ ───────────────────────────────────────────────────────────── │
│ • Evolve Ψ₀ in REAL time (zero velocity) │
│ • Verify amplitude preserved > 0.8 for t = 100 │
│ • Verify energy drift < 1% │
│ • If fails: return to Test 0 with adjusted parameters │
└─────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ TEST 1 (revised): Velocity Saturation │
│ ───────────────────────────────────────────────────────────── │
│ • Boost Ψ₀: Ψ = Ψ₀·exp(ikz), Ψ̇ = -v_soliton·∂Ψ₀/∂z │
│ • Sweep velocities: 0.3v to 0.95v │
│ • Measure forward/rear phase gradient asymmetry │
│ • If asymmetry ratio increases with velocity → SUPPORTED │
└─────────────────────────────────────────────────────────────────┘
✅ Final verdict on each AI's contribution
AI Strongest contribution Limitation
Copilot Practical parameter prescription; identified weak nonlinearity Misidentified μ as damping
Gemini Corrected the physics of μ; warned about blow-up Underestimated Copilot's practical fix
ChatGPT Methodological rigor (Test 0 first); identified gradient inconsistencies Most thorough but also most critical
DeepSeek Synthesized all views; produced actionable Test 0 script Needs the corrections above
🚀 Immediate next action
Run Test 0 with the corrections above.
Specifically:
Fix the gradient term (exact derivative)
Verify centrifugal sign (should be + in gradient)
Add residual norm convergence check
After convergence, run Test 0B (real-time stability)
Only after Test 0B passes should you proceed to Test 1.
📊 Success criteria summary
Test Success criteria
Test 0 Energy drift < 1e-8, residual norm < 1e-8, amplitude stable
Test 0B Real-time amplitude stays > 80% of initial for t=100
Test 1 Asymmetry ratio > 1.2 and increases with velocity --->> The is the script running now -> Deepseek -> the corrected, production-ready Test 0 script incorporating all refinements from the four analyses.
python
"""
FRCMFD-v2: TEST 0 — STATIONARY TOROIDAL SOLITON FINDER (CORRECTED)
====================================================================
This script finds a stable stationary toroidal soliton solution using
imaginary-time relaxation (gradient flow) with:
✓ Exact variational derivative (corrected)
✓ Verified centrifugal sign (+ in gradient, consistent with blueprint)
✓ Residual norm convergence check
✓ Real-time stability verification (Test 0B)
METHOD:
∂Ψ/∂τ = -δE/δΨ* (gradient descent in field space)
CANONICAL BLUEPRINT COMPLIANCE:
✅ Pure 1/r² centrifugal term
✅ Self-adjoint operators
✅ Correct saturation function
✅ Variationally consistent gradient
"""
import numpy as np
import scipy.sparse as sp
import matplotlib.pyplot as plt
from datetime import datetime
import json
import warnings
warnings.filterwarnings('ignore')
print("="*80)
print("FRCMFD-v2: TEST 0 — STATIONARY TOROIDAL SOLITON FINDER (CORRECTED)")
print("Imaginary-time relaxation with exact variational gradient")
print("="*80)
# ============================================================
# GRID AND PARAMETERS
# ============================================================
print("\n[Setting up grid and parameters...]")
# Grid
r_max = 40.0
z_max = 40.0
nr_full = 200
nz = 200
dr = r_max / nr_full
dz = z_max / nz
r_grid_full = np.linspace(dr/2, r_max - dr/2, nr_full)
r_grid = r_grid_full[1:] # Remove r=0 Dirichlet DOF
nr = len(r_grid)
z_grid = np.linspace(-z_max/2, z_max/2, nz)
print(f"Grid: nr={nr}, nz={nz} ({nr*nz:,} DOF)")
print(f"Domain: r ∈ [{r_grid[0]:.3f}, {r_grid[-1]:.3f}], z ∈ [{z_grid[0]:.1f}, {z_grid[-1]:.1f}]")
print(f"dr={dr:.4f}, dz={dz:.4f}")
# Physical parameters (SOLITON-SUPPORTING regime)
v = 1.0 # Substrate speed
mu = -1.0 # Linear restoring (mass term)
lam = 0.5 # Nonlinear focusing (moderate)
kappa = 0.25 # Tension coupling (moderate)
m = 1 # Winding mode (toroidal vortex)
S_max = 2.0 # Maximum tension
Psi_sat = 0.8 # Saturation threshold (below peak amplitude)
# Relaxation parameters
dtau = 0.005 # Imaginary time step (reduced for stability)
tau_max = 400.0 # Maximum relaxation time
n_steps = int(tau_max / dtau)
n_save = 200
n_save_print = n_save * 5
print(f"\nPhysical parameters (soliton-supporting regime):")
print(f" v={v:.2f}, μ={mu:.2f}, λ={lam:.2f}, κ={kappa:.2f}")
print(f" S_max={S_max:.2f}, Ψ_sat={Psi_sat:.2f}, m={m}")
print(f" dtau={dtau:.4f}, tau_max={tau_max:.1f}, steps={n_steps:,}")
# ============================================================
# BUILD OPERATORS (Self-adjoint, machine precision)
# ============================================================
print("\n[Building spatial operators...]")
def build_radial_operator(r_grid, dr):
"""Self-adjoint radial Laplacian: L_r = W_r⁻¹ M"""
nr = len(r_grid)
r_face = np.zeros(nr + 1)
r_face[0] = r_grid[0] - dr/2
for i in range(1, nr + 1):
r_face[i] = r_grid[i-1] + dr/2
flux_right = r_face[1:] / dr
flux_left = r_face[:-1] / dr
main_diag = -(flux_left + flux_right)
lower_diag = flux_left[1:]
upper_diag = flux_right[:-1]
M = sp.diags([lower_diag, main_diag, upper_diag], [-1, 0, 1], format='csr')
w_r = r_grid * dr
W_r = sp.diags(w_r, format='csr')
W_r_inv = sp.diags(1.0 / w_r, format='csr')
L_r = W_r_inv @ M
return L_r, W_r
def build_axial_operator(nz, dz):
"""Self-adjoint axial Laplacian (symmetric stencil)"""
main_diag = np.ones(nz) * (-2.0 / dz**2)
upper = np.ones(nz - 1) / dz**2
lower = np.ones(nz - 1) / dz**2
L_z = sp.diags([lower, main_diag, upper], [-1, 0, 1], format='csr')
W_z = sp.diags(np.ones(nz) * dz, format='csr')
return L_z, W_z
L_r, W_r = build_radial_operator(r_grid, dr)
L_z, W_z = build_axial_operator(nz, dz)
I_r = sp.eye(nr, format='csr')
I_z = sp.eye(nz, format='csr')
L_2D = sp.kron(I_z, L_r, format='csr') + sp.kron(L_z, I_r, format='csr')
W_2D = sp.kron(W_z, W_r, format='csr')
W_2D_diag = W_2D.diagonal()
dV = W_2D_diag * 2 * np.pi # Volume element for integration
# Physical radial coordinate
r_mesh_2d = np.tile(r_grid, nz)
print(f"✓ Operators built: L_2D {L_2D.shape}, nnz={L_2D.nnz:,}")
print(f"✓ Volume element size: {len(dV):,} points")
# ============================================================
# INITIAL GUESS (Gaussian ring torus)
# ============================================================
def initial_guess(r_grid, z_grid, R0=6.0, sigma=2.0):
"""Gaussian ring torus as initial guess."""
nz, nr = len(z_grid), len(r_grid)
phi_2d = np.zeros((nz, nr))
for j, z in enumerate(z_grid):
for i, r in enumerate(r_grid):
rho = np.sqrt((r - R0)**2 + z**2)
phi_2d[j, i] = np.exp(-rho**2 / (2 * sigma**2))
phi_2d = phi_2d / np.max(np.abs(phi_2d))
return phi_2d.ravel().astype(complex)
print("\n[Generating initial guess (Gaussian ring torus)...]")
Psi = initial_guess(r_grid, z_grid)
initial_amplitude = np.max(np.abs(Psi))
print(f"✓ Initial max|Ψ| = {initial_amplitude:.4f}")
# ============================================================
# ENERGY AND EXACT GRADIENT (Variationally consistent)
# ============================================================
def compute_energy(Psi, L_2D, dV, r_mesh_2d, v, mu, lam, kappa, S_max, Psi_sat, m):
"""
Compute energy functional E[Ψ] (real-valued).
E = ½∫|Ψ̇|² dV + ½v²∫|∇Ψ|² dV - ½μ∫|Ψ|² dV
+ ¼λ∫|Ψ|⁴ dV + ½κ∫ S(|Ψ|²) |Ψ|² dV
+ ½v²m²∫ |Ψ|²/r² dV
"""
psi_sq = np.abs(Psi)**2
# Kinetic energy (time derivative = 0 for stationary state)
kin_time = 0.0
# Gradient energy: ½v²∫|∇Ψ|² dV = -½v²∫ Ψ* L Ψ dV (since L is negative-definite)
kin_grad = -0.5 * v**2 * np.real(np.sum(np.conj(Psi) * (L_2D @ Psi) * dV))
# Potential energies
pot_mass = -0.5 * mu * np.sum(psi_sq * dV)
pot_nonlinear = 0.25 * lam * np.sum(psi_sq * psi_sq * dV)
S = S_max * np.tanh(psi_sq / (Psi_sat**2))
pot_tension = 0.5 * kappa * np.sum(S * psi_sq * dV)
# Centrifugal potential (positive definite barrier)
pot_centrifugal = 0.5 * v**2 * m**2 * np.sum(psi_sq / (r_mesh_2d**2 + 1e-12) * dV)
return (kin_time + kin_grad + pot_mass + pot_nonlinear + pot_tension + pot_centrifugal).real
def compute_gradient(Psi, L_2D, dV, r_mesh_2d, v, mu, lam, kappa, S_max, Psi_sat, m):
"""
Compute exact variational derivative δE/δΨ*.
For imaginary-time evolution: ∂Ψ/∂τ = -δE/δΨ*
δE/δΨ* = -v² L Ψ + μΨ + λ|Ψ|²Ψ
+ κ [ S(|Ψ|²) + |Ψ|² dS/d|Ψ|² ] Ψ
+ v² m² / r² Ψ
Where dS/d|Ψ|² = (S_max / Ψ_sat²) * sech²(|Ψ|²/Ψ_sat²)
"""
psi_sq = np.abs(Psi)**2
# Saturation function and its derivative
S = S_max * np.tanh(psi_sq / (Psi_sat**2))
dS_dpsi2 = (S_max / (Psi_sat**2)) * (1.0 / np.cosh(psi_sq / (Psi_sat**2))**2)
# Variational derivative components (EXACT)
term_kin = -v**2 * (L_2D @ Psi)
term_mass = mu * Psi
term_nonlinear = lam * psi_sq * Psi
# EXACT derivative of κ S(|Ψ|²) |Ψ|² w.r.t. Ψ*
# = κ [ S + |Ψ|² dS/d|Ψ|² ] Ψ
term_tension_exact = kappa * (S + psi_sq * dS_dpsi2) * Psi
# Centrifugal term (POSITIVE sign in gradient, consistent with energy)
term_centrifugal = +v**2 * m**2 * Psi / (r_mesh_2d**2 + 1e-12)
return term_kin + term_mass + term_nonlinear + term_tension_exact + term_centrifugal
def compute_residual_norm(grad, dV):
"""Compute L2 norm of gradient (convergence metric)."""
return np.sqrt(np.sum(np.abs(grad)**2 * dV))
# ============================================================
# IMAGINARY-TIME RELAXATION
# ============================================================
print("\n" + "="*80)
print("RUNNING IMAGINARY-TIME RELAXATION")
print("∂Ψ/∂τ = -δE/δΨ*")
print("="*80)
times = []
energies = []
max_amps = []
center_amps = []
residuals = []
Psi_current = Psi.copy()
for step in range(n_steps):
# Compute exact gradient
grad = compute_gradient(Psi_current, L_2D, dV, r_mesh_2d,
v, mu, lam, kappa, S_max, Psi_sat, m)
# Imaginary-time step (gradient descent)
Psi_current = Psi_current - dtau * grad
# Save diagnostics
if step % n_save == 0:
tau = step * dtau
times.append(tau)
E = compute_energy(Psi_current, L_2D, dV, r_mesh_2d,
v, mu, lam, kappa, S_max, Psi_sat, m)
energies.append(E)
max_amp = np.max(np.abs(Psi_current))
max_amps.append(max_amp)
# Center amplitude (at r≈R0=6.0, z=0)
Psi_2d = Psi_current.reshape((nz, nr))
mid_z_idx = nz // 2
r_idx = np.argmin(np.abs(r_grid - 6.0))
center_amp = np.abs(Psi_2d[mid_z_idx, r_idx])
center_amps.append(center_amp)
# Residual norm
resid = compute_residual_norm(grad, dV)
residuals.append(resid)
if step % n_save_print == 0 and step > 0:
print(f" τ={tau:6.2f}, E={E:.6e}, max|Ψ|={max_amp:.4f}, "
f"resid={resid:.2e}, center|Ψ|={center_amp:.4f}")
# Early convergence check
if step > n_save * 5 and len(residuals) > 5:
if residuals[-1] < 1e-8 and abs(energies[-1] - energies[-2]) / abs(energies[-1] + 1e-15) < 1e-10:
print(f"\n✓ Convergence reached at τ={step*dtau:.2f}")
break
print(f"\n✓ Relaxation complete after {len(energies)} saved steps")
print(f" Final max|Ψ| = {max_amps[-1]:.4f}")
print(f" Final center|Ψ| = {center_amps[-1]:.4f}")
print(f" Final energy = {energies[-1]:.6e}")
print(f" Final residual norm = {residuals[-1]:.2e}")
Psi_soliton = Psi_current.copy()
# ============================================================
# CONVERGENCE ANALYSIS
# ============================================================
print("\n" + "="*80)
print("CONVERGENCE ANALYSIS")
print("="*80)
# Amplitude stability
if len(max_amps) > 10:
amp_last_10 = max_amps[-10:]
amp_std = np.std(amp_last_10)
amp_mean = np.mean(amp_last_10)
amp_rel_stability = amp_std / amp_mean
print(f"\nAmplitude stability:")
print(f" Mean final amplitude: {amp_mean:.4f}")
print(f" Std deviation (last 10): {amp_std:.4e}")
print(f" Relative stability: {amp_rel_stability:.2e}")
# Residual norm
print(f"\nResidual norm convergence:")
print(f" Initial residual: {residuals[0]:.2e}")
print(f" Final residual: {residuals[-1]:.2e}")
print(f" Reduction factor: {residuals[0]/max(residuals[-1], 1e-15):.2e}")
# Energy convergence
if len(energies) > 5:
energy_drift = (energies[-1] - energies[-5]) / abs(energies[-1] + 1e-15)
print(f"\nEnergy convergence:")
print(f" Final energy: {energies[-1]:.6e}")
print(f" Drift (last 5 steps): {energy_drift:.2e}")
# ============================================================
# TEST 0B: REAL-TIME STABILITY VERIFICATION
# ============================================================
print("\n" + "="*80)
print("TEST 0B: REAL-TIME STABILITY VERIFICATION")
print("Evolving stationary soliton in real time (zero velocity)")
print("="*80)
# Real-time parameters
dt_real = 0.0025
t_max_real = 100.0
n_steps_real = int(t_max_real / dt_real)
n_save_real = 200
def acceleration_real(Psi, L_2D, r_mesh_2d, v, mu, lam, kappa, S_max, Psi_sat, m):
"""Real-time acceleration (canonical equation)."""
psi_sq = np.abs(Psi)**2
S = S_max * np.tanh(psi_sq / (Psi_sat**2))
lap_term = v**2 * (L_2D @ Psi)
mass_term = mu * Psi
nonlinear_term = lam * psi_sq * Psi
tension_term = kappa * S * Psi
centrifugal_term = -v**2 * m**2 * Psi / (r_mesh_2d**2 + 1e-12)
return lap_term + mass_term + nonlinear_term + tension_term + centrifugal_term
# Initialize
Psi_test = Psi_soliton.copy()
Psi_dot_test = np.zeros_like(Psi_test)
times_real = []
max_amps_real = []
energies_real = []
print("\n[Running real-time evolution...]")
for step in range(n_steps_real):
acc = acceleration_real(Psi_test, L_2D, r_mesh_2d, v, mu, lam, kappa, S_max, Psi_sat, m)
Psi_dot_half = Psi_dot_test + 0.5 * dt_real * acc
Psi_test_new = Psi_test + dt_real * Psi_dot_half
acc_next = acceleration_real(Psi_test_new, L_2D, r_mesh_2d, v, mu, lam, kappa, S_max, Psi_sat, m)
Psi_dot_test = Psi_dot_half + 0.5 * dt_real * acc_next
Psi_test = Psi_test_new
if step % n_save_real == 0:
t = step * dt_real
times_real.append(t)
max_amps_real.append(np.max(np.abs(Psi_test)))
E = compute_energy(Psi_test, L_2D, dV, r_mesh_2d, v, mu, lam, kappa, S_max, Psi_sat, m)
energies_real.append(E)
if step % (n_save_real * 10) == 0 and step > 0:
print(f" t={t:6.2f}, max|Ψ|={max_amps_real[-1]:.4f}, E={E:.6e}")
# Real-time stability analysis
amp_initial_real = max_amps_real[0]
amp_final_real = max_amps_real[-1]
amp_preservation = amp_final_real / amp_initial_real
energy_initial_real = energies_real[0]
energy_final_real = energies_real[-1]
energy_drift_real = (energy_final_real - energy_initial_real) / abs(energy_initial_real) * 100
print(f"\nReal-time stability results:")
print(f" Initial max|Ψ|: {amp_initial_real:.4f}")
print(f" Final max|Ψ|: {amp_final_real:.4f}")
print(f" Amplitude preservation: {amp_preservation*100:.1f}%")
print(f" Energy drift: {energy_drift_real:.4f}%")
# ============================================================
# VISUALIZATION
# ============================================================
print("\n[Generating plots...]")
fig, axes = plt.subplots(2, 4, figsize=(18, 10))
# Row 0: Convergence diagnostics
axes[0, 0].semilogy(times, energies, 'b-', linewidth=2)
axes[0, 0].set_xlabel('Imaginary time τ')
axes[0, 0].set_ylabel('Energy E[Ψ]')
axes[0, 0].set_title('Energy Minimization')
axes[0, 0].grid(True, alpha=0.3)
axes[0, 1].plot(times, max_amps, 'r-', linewidth=2)
axes[0, 1].set_xlabel('Imaginary time τ')
axes[0, 1].set_ylabel('max|Ψ|')
axes[0, 1].set_title('Amplitude Evolution')
axes[0, 1].grid(True, alpha=0.3)
axes[0, 2].semilogy(times, residuals, 'm-', linewidth=2)
axes[0, 2].set_xlabel('Imaginary time τ')
axes[0, 2].set_ylabel('Residual norm')
axes[0, 2].set_title('Gradient Norm Convergence')
axes[0, 2].grid(True, alpha=0.3)
axes[0, 3].plot(times, center_amps, 'g-', linewidth=2)
axes[0, 3].set_xlabel('Imaginary time τ')
axes[0, 3].set_ylabel('Center |Ψ|')
axes[0, 3].set_title('Core Amplitude Evolution')
axes[0, 3].grid(True, alpha=0.3)
# Row 1: Final soliton structure
Psi_final_2d = Psi_soliton.reshape((nz, nr))
extent = [r_grid[0], r_grid[-1], z_grid[0], z_grid[-1]]
im1 = axes[1, 0].imshow(np.abs(Psi_final_2d).T, origin='lower',
extent=extent, aspect='auto', cmap='viridis')
axes[1, 0].set_xlabel('r')
axes[1, 0].set_ylabel('z')
axes[1, 0].set_title('Final |Ψ| (stationary soliton)')
plt.colorbar(im1, ax=axes[1, 0])
im2 = axes[1, 1].imshow(np.angle(Psi_final_2d).T, origin='lower',
extent=extent, aspect='auto', cmap='twilight')
axes[1, 1].set_xlabel('r')
axes[1, 1].set_ylabel('z')
axes[1, 1].set_title('Final phase arg(Ψ)')
plt.colorbar(im2, ax=axes[1, 1])
# Radial profile at z=0
mid_z_idx = nz // 2
radial_profile = np.abs(Psi_final_2d[mid_z_idx, :])
axes[1, 2].plot(r_grid, radial_profile, 'b-', linewidth=2)
axes[1, 2].set_xlabel('r')
axes[1, 2].set_ylabel('|Ψ|')
axes[1, 2].set_title('Radial profile (z=0)')
axes[1, 2].grid(True, alpha=0.3)
axes[1, 2].axvline(6.0, color='r', linestyle='--', alpha=0.5, label='R₀=6.0')
axes[1, 2].legend()
# Real-time stability
axes[1, 3].plot(times_real, max_amps_real, 'b-', linewidth=2)
axes[1, 3].set_xlabel('Time t')
axes[1, 3].set_ylabel('max|Ψ|')
axes[1, 3].set_title(f'Real-time Stability ({amp_preservation*100:.1f}% preserved)')
axes[1, 3].grid(True, alpha=0.3)
axes[1, 3].axhline(amp_initial_real * 0.8, color='r', linestyle='--', alpha=0.5, label='80% threshold')
plt.tight_layout()
plt.savefig('test_0_stationary_soliton.png', dpi=150)
print("✓ Plot saved: test_0_stationary_soliton.png")
# ============================================================
# SAVE RESULTS
# ============================================================
print("\n[Saving results...]")
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
test_name = f"test_0_stationary_soliton_{timestamp}"
# Save final soliton
np.savez(f"{test_name}_soliton.npz",
Psi_soliton=Psi_soliton,
r_grid=r_grid,
z_grid=z_grid,
dr=dr, dz=dz,
v=v, mu=mu, lam=lam, kappa=kappa,
S_max=S_max, Psi_sat=Psi_sat, m=m)
# Save metadata
results = {
'test_name': 'Test 0: Stationary Toroidal Soliton Finder (Corrected)',
'timestamp': timestamp,
'method': 'Imaginary-time relaxation with exact variational gradient',
'parameters': {
'v': v, 'mu': mu, 'lambda': lam, 'kappa': kappa,
'S_max': S_max, 'Psi_sat': Psi_sat, 'm': m,
'dtau': dtau, 'tau_max': tau_max,
'nr': nr, 'nz': nz, 'dr': dr, 'dz': dz,
},
'convergence': {
'final_energy': float(energies[-1]),
'final_max_amp': float(max_amps[-1]),
'final_center_amp': float(center_amps[-1]),
'final_residual': float(residuals[-1]),
'amplitude_stability': float(amp_rel_stability) if len(max_amps) > 10 else None,
'energy_drift_final': float(energy_drift) if len(energies) > 5 else None,
},
'real_time_stability': {
'amplitude_preservation': float(amp_preservation),
'energy_drift_percent': float(energy_drift_real),
'final_amplitude': float(amp_final_real),
},
'success': amp_preservation > 0.8 and residuals[-1] < 1e-8
}
with open(f"{test_name}_results.json", 'w') as f:
json.dump(results, f, indent=2, default=str)
print(f"✓ Soliton saved: {test_name}_soliton.npz")
print(f"✓ Results saved: {test_name}_results.json")
# ============================================================
# VERDICT
# ============================================================
print("\n" + "="*80)
print("TEST 0 COMPLETE")
print("="*80)
# Determine success
converged = residuals[-1] < 1e-8
stable = amp_preservation > 0.8
if converged and stable:
print("\n✅ STATIONARY SOLITON FOUND AND STABLE")
print(" ✓ Gradient converged (residual < 1e-8)")
print(f" ✓ Real-time amplitude preservation: {amp_preservation*100:.1f}%")
print("\n📋 NEXT STEP: Run Test 1 (Velocity Saturation)")
print(" Load the saved soliton and apply velocity boost:")
print(" Psi_boosted = Psi_soliton * exp(1j * k * z)")
print(" Psi_dot = -v_soliton * ∂Ψ/∂z")
verdict = "SOLITON_FOUND_STABLE"
elif converged and not stable:
print("\n⚠️ SOLITON FOUND BUT UNSTABLE IN REAL TIME")
print(" The imaginary-time relaxation converged but the state")
print(" does not persist under real-time evolution.")
print(" Adjust parameters (λ, κ, S_max, Ψ_sat) and retry.")
verdict = "UNSTABLE"
else:
print("\n❌ SOLITON NOT FOUND")
print(" Residual norm did not converge to threshold.")
print(" Adjust parameters and increase tau_max.")
verdict = "NOT_FOUND"
print(f"\n📊 VERDICT: {verdict}")
print("="*80)
✅ What this corrected script includes
Feature Status
Exact variational gradient (with S + |Ψ|² dS/d|Ψ|²) ✅
Correct centrifugal sign (+ in gradient) ✅
Residual norm convergence check ✅
Test 0B: Real-time stability verification ✅
80% amplitude preservation threshold ✅
Comprehensive plotting (convergence + structure + stability) ✅
JSON metadata with success flag ✅
🚀 How to run
python
# In Colab, paste the entire script and execute
exec(open('test_0_stationary_soliton_corrected.py').read())
The script will:
Run imaginary-time relaxation until convergence
Verify real-time stability (Test 0B)
Save the stationary soliton to .npz
Output a clear verdict: SOLITON_FOUND_STABLE / UNSTABLE / NOT_FOUND
📋 After successful Test 0
Load the soliton and proceed to Test 1:
python
# Load stationary soliton
data = np.load('test_0_stationary_soliton__soliton.npz')
Psi_soliton = data['Psi_soliton']
r_grid = data['r_grid']
z_grid = data['z_grid']
# Boost for Test 1
k = v_soliton / v # wave number from dispersion
Psi_boosted = Psi_soliton * np.exp(1j * k * z_grid.reshape(-1, 1))
# Initialize Psi_dot (use finite difference)
Psi_dot = -v_soliton * np.gradient(Psi_boosted.reshape(nz, nr), dz, axis=0).ravel()