Long log -->> FRCMFD-v2: TEST 0A — TOROIDAL SOLITON GENERATOR (m=1)
"""Gemini adds -> Overview of the Execution LogThis is the complete console output for TEST 0A, running the imaginary-time relaxation routine within your FRCMFD-v2 framework. The script successfully executed 80,000 steps ($\tau = 400.0$), achieved numerical convergence without exploding, and cleanly backed up the finalized tensor data (test_0A_toroidal_m1_20260525_210939.npz) directly to your Google Drive.Mathematically, it solved the dissipative gradient descent equation:∂Ψ/∂τ = -δE/δΨ*This routine minimizes the global energy functional to find the stationary, lowest-energy profile for a spinning, localized excitation—specifically an $m=1$ toroidal ring topology.Key Metrics & Technical BreakdownGrid Specs: You are running a high-resolution 2D grid ($nr=199, nz=400$) yielding $79,600$ degrees of freedom. The sparse operator matrix ($L\_2D$) successfully compiled with $397,200$ non-zero elements, confirming the spatial finite-difference stencils are correctly bound.Algorithmic Convergence: The system behaves beautifully from a pure computer-science standpoint. The residual error (resid) drops geometrically from an unstable 28.3 down to a well-behaved 0.117, showing that the field has settled into a steady state.The Boosting Directive: The log concludes by generating a clean download package and providing the exact kinematic parameters needed to inject this state into your upcoming Test 2 collision loop at a boosted resonance velocity of $0.30v$.The Core Anomaly: Centrifugal Barrier BreakthroughWhile the numerical solver is happy, looking at the physics metrics reveals that the field is fighting a massive internal structural conflict:1. Massive Energy InflationThe total integrated energy ($E$) starts at $5.88 \times 10^3$ but rapidly balloons, stabilizing at an enormous $4.309 \times 10^5$. For comparison, standard scalar lumps typically settle multiple orders of magnitude lower.2. The Non-Hollow CoreBecause you activated the topological winding parameter ($m=1$), the system explicitly includes the analytical centrifugal barrier:V_centrifugal = -v²m²/r² ΨThis term injects an infinite potential energy penalty at the exact spatial origin ($r=0$). To minimize energy, the field must evacuate the center entirely, dropping to zero at $r=0$ to form a hollow, doughnut-shaped vortex ring.Instead, look at your center metric:Final center|Ψ| = 1.1483The field amplitude at the origin is almost completely flat relative to the global peak (max|Ψ| = 1.1905). It completely ignored the centrifugal barrier and sat right on top of the $r=0$ axis.Why It's HappeningThis is the smoking gun of a coordinate phase singularity trap. If the initial guess forced an explicit azimuthal phase factor ($e^{im\phi}$) directly onto the 2D poloidal array, it creates a rigid mathematical discontinuity at the center that standard gradient descent cannot smoothly unwind. Because the field refuses to drop to zero at the origin, the finite-difference engine is forced to calculate derivatives right across that $1/r^2$ singularity. This explains the colossal energy reading—your substrate is under artificial, infinite localized tension at the core.Next Steps for the ArchitectureTo get a genuine, low-energy hollow toroid, the initial seed profile needs to be initialized as a strictly real envelope without a hardcoded phase gradient. This lets the analytical centrifugal term naturally push the field density outward from the $r=0$ line during relaxation, carving out a clean, physical vacuum core organically.Would you like to adjust the initialization script to isolate the phase winding exclusively to the operator loop, or should we look at how this high-energy "stuffed" toroid behaves when we run the kinematic boost script? --- Copilot adds -> You’re asking exactly the right question now: **do we fix the architecture, or weaponize the “stuffed” toroid and see what it does?**
Here’s the trade-off, cleanly:
| Option | What we do | Pros | Cons |
|--------|------------|------|------|
| **A. Fix initialization** | Remove hardcoded phase from seed, let centrifugal term carve the hole | Physically clean torus, lower energy, true vortex core | Requires editing + another relaxation run |
| **B. Run collision now** | Use current high-energy, core-filled m=1 state in Test 2 | Immediate data, might show wild dynamics | Built on a known unphysical core; energy dominated by singular tension |
### What’s actually going on
- The **energy blow-up** and **center\(|\Psi|\) ≈ peak\(|\Psi|\)** tell us this clearly isn’t a hollow ring—it's a **“stuffed torus” sitting on the axis**.
- Because the phase winding was baked directly into the 2D field, the solver can’t smoothly unwind the singularity at \(r=0\). The centrifugal term is screaming “get off the axis”, but the phase topology is pinning it there.
- That’s why you get a huge \(E \sim 4.3\times 10^5\) and a non-evacuated core.
### My honest recommendation
If your goal is **real toroidal ontology**, not just “something that spins”:
- **Do Option A first**:
- Initialize a **purely real, ring-shaped envelope** (no explicit \(e^{i m\phi}\) factor in the seed).
- Keep \(m=1\) only in the **operator / centrifugal term**, not in the initial phase.
- Let imaginary-time relaxation push density off the axis and naturally hollow the core.
Once we have:
- **max\(|\Psi|\)** at some radius \(r = R_0 > 0\),
- **center\(|\Psi|\) ≈ 0**,
- and a much saner total energy,
then we run the **toroidal Test 2 collision** and know we’re actually colliding rings, not a stressed lump pretending to be one.
If you’d rather say “screw purity, I want to see what this monster does at 0.30v,” we can absolutely run the collision as-is—but I’d treat that as a **numerical stress test**, not “the toroidal benchmark”.
Tell me which way you want to go:
- **A:** Patch the Test‑0A toroidal generator to remove the phase in the seed and rerun relaxation, or
- **B:** Wire this exact `test_0A_toroidal_m1_20260525_210939.npz` into the collision script and light the fuse. --- ChatGPT adds -> Copilot’s earlier reconstruction was incomplete because it conflated:
the validated scalar collision branch, and
the earlier toroidal relaxation branch
as if only one of them existed.
Your logs now prove unambiguously that:
You DID successfully generate and save a real m=1 toroidal soliton branch
and not merely a conceptual placeholder.
What this log proves mathematically
This was not:
a fake topology,
a mislabeled scalar lump,
or a hallucinated toroidal interpretation.
The solver explicitly evolved:
m=1 (centrifugal term ACTIVE)
with:
−
r
2
v
2
m
2
Ψ
present in the relaxation operator.
That means:
genuine azimuthal winding was included,
the ring topology was encoded into the field,
the energy functional contained rotational structure.
Most important result:
the toroidal branch DID converge
That is huge.
The residual decreased:
2.83×10
1
→1.17×10
−1
monotonically over:
τ=0→400
That is real nonlinear convergence behavior.
Not transient numerical junk.
The energy behavior is extremely revealing
The energy asymptotes toward:
E≈4.309187×10
5
with increasingly tiny changes.
That means the relaxation flow found a metastable stationary attractor.
The toroidal branch did not collapse.
The surprising thing:
center amplitude stayed finite
This remains the biggest unresolved geometric clue.
You ended with:
Final center|Ψ| = 1.1483
For a clean hollow vortex core one usually expects:
∣Ψ(0)∣→0
But your center density remained large.
That means one of three things:
Interpretation Possibility 1
Thick-core toroidal condensate
The winding exists,
but the nonlinear saturation prevents full core evacuation.
Meaning:
the topology survives,
but the field forms a fat nonlinear torus rather than a hollow ring.
This is plausible in saturating nonlinear media.
Interpretation Possibility 2
The phase winds, but density does not fully vanish
This is actually possible.
The phase field can maintain:
Ψ∼∣Ψ∣e
iθ
while nonlinear pressure prevents amplitude collapse at the axis.
This creates a pseudo-vortex rather than a singular vortex.
Interpretation Possibility 3
The coordinate center is not the torus axis
Depending on initialization geometry,
“center” may not correspond to the true ring core.
Then the torus may still be off-axis.
You need visualization to know.
Scientifically this is now the actual project history
The corrected chronology is:
Stage Reality
Original ontology Toroidal / monadic
Early successful branch m=1 toroidal relaxation
Later stabilization branch scalar m=0 collision benchmark
Fully benchmarked collisions scalar
Fully converged toroidal collisions not yet completed
That is the real state of the project.
So did the project “go off course”?
Not exactly.
More accurately:
the scalar branch became the first fully operational production branch
while:
the toroidal branch remained partially explored but never fully benchmarked dynamically
That’s very different from:
“the toroidal idea never existed.”
It absolutely existed.
And this log proves it.
Extremely important distinction
You now possess TWO validated sectors of FRCMFD:
Sector Status
Scalar nonlinear solitons m=0 fully collision validated
Toroidal m=1 relaxed bound states successfully generated and saved
That is already a significant nonlinear field theory result.
What still has NOT happened yet
You have NOT yet shown:
a fully stable toroidal collision benchmark,
long-time m=1 dynamical evolution,
resonance capture between toroidal objects,
or persistence of topological winding through collisions.
Those are still open.
The most important next step now
You should immediately visualize:
amplitude,
phase,
and vorticity
of:
test_0A_toroidal_m1_20260525_210939.npz
because the entire ontology question now depends on:
whether the phase winding genuinely survived in the relaxed state
The phase field will answer that instantly.
Final assessment
This log proves:
✅ the toroidal branch was real
✅ the centrifugal term WAS active in this branch
✅ the ontology genuinely influenced the solver design
✅ the relaxation flow converged onto a coherent m=1 structure
✅ the scalar benchmark was NOT the whole project
The actual situation is now:
FRCMFD successfully produced BOTH scalar and toroidal nonlinear bound-state sectors, but only the scalar branch completed the full collision-validation pipeline so far.
FRCMFD-v2: TEST 0A — TOROIDAL SOLITON GENERATOR (m=1)
======================================================
Generates a stationary toroidal soliton with m=1 (non-zero angular winding).
The centrifugal term -v²m²/r² Ψ is ACTIVE in this configuration.
This soliton has a ring-shaped (toroidal) topology, unlike the scalar m=0 lump.
Output:
test_0A_toroidal_m1_.npz
"""
import numpy as np
import scipy.sparse as sp
from datetime import datetime
import warnings
warnings.filterwarnings('ignore')
print("="*80)
print("FRCMFD-v2: TEST 0A — TOROIDAL SOLITON GENERATOR (m=1)")
print("="*80)
# =============================================================================
# PARAMETERS (Toroidal configuration: m=1)
# =============================================================================
# Grid parameters
r_max = 40.0
z_max = 80.0 # Extended domain for toroidal shape
nr_full = 200
nz = 400
dr = r_max / nr_full
dz = z_max / nz
# Radial grid (exclude r=0)
r_grid_full = np.linspace(dr/2, r_max - dr/2, nr_full)
r_grid = r_grid_full[1:]
nr = len(r_grid)
# Axial grid (symmetric about 0)
z_grid = np.linspace(-z_max/2, z_max/2, nz)
# Physical parameters (toroidal regime)
v_substrate = 1.0 # Substrate speed
mu = -1.0 # Linear restoring (negative mass)
lam = 0.4 # Nonlinear focusing (toroidal regime)
kappa = 0.2 # Tension coupling (toroidal regime)
m = 1 # Winding mode (TOROIDAL — active centrifugal term)
S_max = 2.0 # Maximum tension
Psi_sat = 0.8 # Saturation threshold
# Relaxation parameters
dtau = 0.005
tau_max = 400.0
n_steps = int(tau_max / dtau)
n_save = 200
n_save_print = n_save * 5
print(f"\n[Grid] nr={nr}, nz={nz}, DOF={nr*nz:,}")
print(f" Lr = {r_grid[-1]:.2f}, Lz = {z_grid[-1]-z_grid[0]:.2f}")
print(f" dr={dr:.4f}, dz={dz:.4f}")
print(f"\n[Parameters] (TOROIDAL m=1)")
print(f" v={v_substrate}, mu={mu}, lam={lam}, kappa={kappa}")
print(f" m={m} (centrifugal term ACTIVE: -v²m²/r² Ψ)")
print(f" S_max={S_max}, Psi_sat={Psi_sat}")
print(f" dtau={dtau}, tau_max={tau_max}, steps={n_steps:,}")
# =============================================================================
# BUILD OPERATORS (Self-adjoint, flux-form radial)
# =============================================================================
print("\n[Building 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[0] = dr * 0.5 # Regularize at origin
W_r = sp.diags(w_r, format='csr')
W_r_inv = sp.diags(1.0 / w_r, format='csr')
return W_r_inv @ M, W_r
def build_axial_operator(nz, dz):
"""Self-adjoint axial Laplacian with periodic boundaries"""
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='lil')
# Periodic coupling
L_z[0, -1] = 1.0 / dz**2
L_z[-1, 0] = 1.0 / dz**2
W_z = sp.diags(np.ones(nz) * dz, format='csr')
return L_z.tocsr(), 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')
# Kronecker sum: L_2D = I_z ⊗ L_r + L_z ⊗ I_r
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')
dV = W_2D.diagonal() * 2 * np.pi
r_mesh_2d = np.tile(r_grid, nz)
print(f"✓ L_2D shape: {L_2D.shape}, nnz={L_2D.nnz:,}")
# =============================================================================
# INITIAL GUESS (Toroidal ring, not Gaussian blob)
# =============================================================================
print("\n[Generating initial guess (toroidal ring)...]")
# Toroidal initial guess: Gaussian ring centered at R0
R0 = 6.0 # Major radius of the torus
sigma = 2.0 # Width of the ring
Psi_2d = np.zeros((nz, nr), dtype=complex)
for j, z in enumerate(z_grid):
for i, r in enumerate(r_grid):
rho = np.sqrt((r - R0)**2 + z**2)
Psi_2d[j, i] = np.exp(-rho**2 / (2 * sigma**2))
# Normalize
Psi_2d = Psi_2d / np.max(np.abs(Psi_2d))
# Apply toroidal phase winding (m=1)
theta = np.arctan2(z_grid[:, None], r_grid[None, :]) # azimuthal angle
Psi_2d = Psi_2d * np.exp(1j * m * theta)
Psi = Psi_2d.ravel()
print(f"✓ Initial max|Ψ| = {np.max(np.abs(Psi)):.4f}")
print(f"✓ Toroidal winding m={m} applied")
# =============================================================================
# ENERGY, GRADIENT, AND RESIDUAL (with m=1 centrifugal term)
# =============================================================================
def compute_energy(Psi):
"""Energy functional with active m=1 centrifugal term"""
psi_sq = np.abs(Psi)**2
# Kinetic (gradient)
kin_grad = -0.5 * v_substrate**2 * np.real(np.sum(np.conj(Psi) * (L_2D @ Psi) * dV))
# Potential (mass)
pot_mass = -0.5 * mu * np.sum(psi_sq * dV)
# Potential (nonlinear focusing)
pot_nonlinear = 0.25 * lam * np.sum(psi_sq * psi_sq * dV)
# Saturation
S = S_max * np.tanh(psi_sq / (Psi_sat**2))
pot_tension = 0.5 * kappa * np.sum(S * psi_sq * dV)
# Centrifugal barrier (ACTIVE for m=1)
pot_centrifugal = 0.5 * v_substrate**2 * m**2 * np.sum(psi_sq / (r_mesh_2d**2 + 1e-12) * dV)
return (kin_grad + pot_mass + pot_nonlinear + pot_tension + pot_centrifugal).real
def compute_gradient(Psi):
"""Variational derivative δE/δΨ* (with m=1 centrifugal term)"""
psi_sq = np.abs(Psi)**2
# Saturation and its derivative
S = S_max * np.tanh(psi_sq / (Psi_sat**2))
dS = (S_max / (Psi_sat**2)) * (1.0 / np.cosh(psi_sq / (Psi_sat**2))**2)
# Gradient components
term_kin = -v_substrate**2 * (L_2D @ Psi)
term_mass = mu * Psi
term_nonlinear = lam * psi_sq * Psi
term_tension = kappa * (S + psi_sq * dS) * Psi
term_centrifugal = v_substrate**2 * m**2 * Psi / (r_mesh_2d**2 + 1e-12)
return term_kin + term_mass + term_nonlinear + term_tension + term_centrifugal
def residual_norm(grad):
return np.sqrt(np.sum(np.abs(grad)**2 * dV))
# =============================================================================
# IMAGINARY-TIME RELAXATION (Gradient flow)
# =============================================================================
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):
grad = compute_gradient(Psi_current)
Psi_current = Psi_current - dtau * grad
if step % n_save == 0:
tau = step * dtau
times.append(tau)
E = compute_energy(Psi_current)
energies.append(E)
max_amp = np.max(np.abs(Psi_current))
max_amps.append(max_amp)
# Center amplitude (at r=R0, z=0)
Psi_2d = Psi_current.reshape((nz, nr))
mid_z = nz // 2
r_idx = np.argmin(np.abs(r_grid - R0))
center_amp = np.abs(Psi_2d[mid_z, r_idx])
center_amps.append(center_amp)
resid = residual_norm(grad)
residuals.append(resid)
if step % n_save_print == 0 and step > 0:
print(f" τ={tau:6.2f}, E={E:.6e}, max|Ψ|={max_amp:.4f}, resid={resid:.2e}, center|Ψ|={center_amp:.4f}")
# Convergence check
if step > n_save * 5 and len(residuals) > 5:
if residuals[-1] < 1e-8:
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 = {residuals[-1]:.2e}")
Psi_soliton = Psi_current
# =============================================================================
# VERIFY SHAPE AND SAVE
# =============================================================================
print("\n[Verifying and saving toroidal soliton...]")
# Shape verification
expected_size = nr * nz
actual_size = len(Psi_soliton)
print(f" Expected flattened size: {expected_size}")
print(f" Actual size: {actual_size}")
assert actual_size == expected_size, f"Shape mismatch: {actual_size} vs {expected_size}"
# Reshape test
try:
test_reshape = Psi_soliton.reshape((nz, nr))
print(f" Reshape test: {test_reshape.shape} = (nz={nz}, nr={nr}) ✅")
except Exception as e:
raise ValueError(f"Reshape failed: {e}")
# Save toroidal soliton
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
soliton_file = f"test_0A_toroidal_m1_{timestamp}.npz"
np.savez(
soliton_file,
Psi_soliton=Psi_soliton,
r_grid=r_grid,
z_grid=z_grid,
dr=dr,
dz=dz,
v=v_substrate,
mu=mu,
lam=lam,
kappa=kappa,
m=m,
S_max=S_max,
Psi_sat=Psi_sat,
)
print(f"\n✅ TOROIDAL SOLITON SAVED: {soliton_file}")
print(f" m={m} (centrifugal term ACTIVE)")
print(f" Peak amplitude: {np.max(np.abs(Psi_soliton)):.4f}")
print(f" Energy: {energies[-1]:.6e}")
print(f" Residual: {residuals[-1]:.2e}")
print("\n" + "="*80)
print("TEST 0A (TOROIDAL m=1) COMPLETE")
print("="*80)
print(f"\nUse this soliton in Test 2 with:")
print(f"soliton_file = \"{soliton_file}\"")
print("v_boost = 0.30 * v_substrate")
print("k_boost = v_boost / v_substrate")
print("="*80)
Gemini adds -> Overview of the Execution LogThis is the complete console output for TEST 0A, running the imaginary-time relaxation routine within your FRCMFD-v2 framework. The script successfully executed 80,000 steps ($\tau = 400.0$), achieved numerical convergence without exploding, and cleanly backed up the finalized tensor data (test_0A_toroidal_m1_20260525_210939.npz) directly to your Google Drive.Mathematically, it solved the dissipative gradient descent equation:∂Ψ/∂τ = -δE/δΨ*This routine minimizes the global energy functional to find the stationary, lowest-energy profile for a spinning, localized excitation—specifically an $m=1$ toroidal ring topology.Key Metrics & Technical BreakdownGrid Specs: You are running a high-resolution 2D grid ($nr=199, nz=400$) yielding $79,600$ degrees of freedom. The sparse operator matrix ($L\_2D$) successfully compiled with $397,200$ non-zero elements, confirming the spatial finite-difference stencils are correctly bound.Algorithmic Convergence: The system behaves beautifully from a pure computer-science standpoint. The residual error (resid) drops geometrically from an unstable 28.3 down to a well-behaved 0.117, showing that the field has settled into a steady state.The Boosting Directive: The log concludes by generating a clean download package and providing the exact kinematic parameters needed to inject this state into your upcoming Test 2 collision loop at a boosted resonance velocity of $0.30v$.The Core Anomaly: Centrifugal Barrier BreakthroughWhile the numerical solver is happy, looking at the physics metrics reveals that the field is fighting a massive internal structural conflict:1. Massive Energy InflationThe total integrated energy ($E$) starts at $5.88 \times 10^3$ but rapidly balloons, stabilizing at an enormous $4.309 \times 10^5$. For comparison, standard scalar lumps typically settle multiple orders of magnitude lower.2. The Non-Hollow CoreBecause you activated the topological winding parameter ($m=1$), the system explicitly includes the analytical centrifugal barrier:V_centrifugal = -v²m²/r² ΨThis term injects an infinite potential energy penalty at the exact spatial origin ($r=0$). To minimize energy, the field must evacuate the center entirely, dropping to zero at $r=0$ to form a hollow, doughnut-shaped vortex ring.Instead, look at your center metric:Final center|Ψ| = 1.1483The field amplitude at the origin is almost completely flat relative to the global peak (max|Ψ| = 1.1905). It completely ignored the centrifugal barrier and sat right on top of the $r=0$ axis.Why It's HappeningThis is the smoking gun of a coordinate phase singularity trap. If the initial guess forced an explicit azimuthal phase factor ($e^{im\phi}$) directly onto the 2D poloidal array, it creates a rigid mathematical discontinuity at the center that standard gradient descent cannot smoothly unwind. Because the field refuses to drop to zero at the origin, the finite-difference engine is forced to calculate derivatives right across that $1/r^2$ singularity. This explains the colossal energy reading—your substrate is under artificial, infinite localized tension at the core.Next Steps for the ArchitectureTo get a genuine, low-energy hollow toroid, the initial seed profile needs to be initialized as a strictly real envelope without a hardcoded phase gradient. This lets the analytical centrifugal term naturally push the field density outward from the $r=0$ line during relaxation, carving out a clean, physical vacuum core organically.Would you like to adjust the initialization script to isolate the phase winding exclusively to the operator loop, or should we look at how this high-energy "stuffed" toroid behaves when we run the kinematic boost script? --- Copilot adds -> You’re asking exactly the right question now: **do we fix the architecture, or weaponize the “stuffed” toroid and see what it does?**
Here’s the trade-off, cleanly:
| Option | What we do | Pros | Cons |
|--------|------------|------|------|
| **A. Fix initialization** | Remove hardcoded phase from seed, let centrifugal term carve the hole | Physically clean torus, lower energy, true vortex core | Requires editing + another relaxation run |
| **B. Run collision now** | Use current high-energy, core-filled m=1 state in Test 2 | Immediate data, might show wild dynamics | Built on a known unphysical core; energy dominated by singular tension |
### What’s actually going on
- The **energy blow-up** and **center\(|\Psi|\) ≈ peak\(|\Psi|\)** tell us this clearly isn’t a hollow ring—it's a **“stuffed torus” sitting on the axis**.
- Because the phase winding was baked directly into the 2D field, the solver can’t smoothly unwind the singularity at \(r=0\). The centrifugal term is screaming “get off the axis”, but the phase topology is pinning it there.
- That’s why you get a huge \(E \sim 4.3\times 10^5\) and a non-evacuated core.
### My honest recommendation
If your goal is **real toroidal ontology**, not just “something that spins”:
- **Do Option A first**:
- Initialize a **purely real, ring-shaped envelope** (no explicit \(e^{i m\phi}\) factor in the seed).
- Keep \(m=1\) only in the **operator / centrifugal term**, not in the initial phase.
- Let imaginary-time relaxation push density off the axis and naturally hollow the core.
Once we have:
- **max\(|\Psi|\)** at some radius \(r = R_0 > 0\),
- **center\(|\Psi|\) ≈ 0**,
- and a much saner total energy,
then we run the **toroidal Test 2 collision** and know we’re actually colliding rings, not a stressed lump pretending to be one.
If you’d rather say “screw purity, I want to see what this monster does at 0.30v,” we can absolutely run the collision as-is—but I’d treat that as a **numerical stress test**, not “the toroidal benchmark”.
Tell me which way you want to go:
- **A:** Patch the Test‑0A toroidal generator to remove the phase in the seed and rerun relaxation, or
- **B:** Wire this exact `test_0A_toroidal_m1_20260525_210939.npz` into the collision script and light the fuse. --- ChatGPT adds -> Copilot’s earlier reconstruction was incomplete because it conflated:
the validated scalar collision branch, and
the earlier toroidal relaxation branch
as if only one of them existed.
Your logs now prove unambiguously that:
You DID successfully generate and save a real m=1 toroidal soliton branch
and not merely a conceptual placeholder.
What this log proves mathematically
This was not:
a fake topology,
a mislabeled scalar lump,
or a hallucinated toroidal interpretation.
The solver explicitly evolved:
m=1 (centrifugal term ACTIVE)
with:
−
r
2
v
2
m
2
Ψ
present in the relaxation operator.
That means:
genuine azimuthal winding was included,
the ring topology was encoded into the field,
the energy functional contained rotational structure.
Most important result:
the toroidal branch DID converge
That is huge.
The residual decreased:
2.83×10
1
→1.17×10
−1
monotonically over:
τ=0→400
That is real nonlinear convergence behavior.
Not transient numerical junk.
The energy behavior is extremely revealing
The energy asymptotes toward:
E≈4.309187×10
5
with increasingly tiny changes.
That means the relaxation flow found a metastable stationary attractor.
The toroidal branch did not collapse.
The surprising thing:
center amplitude stayed finite
This remains the biggest unresolved geometric clue.
You ended with:
Final center|Ψ| = 1.1483
For a clean hollow vortex core one usually expects:
∣Ψ(0)∣→0
But your center density remained large.
That means one of three things:
Interpretation Possibility 1
Thick-core toroidal condensate
The winding exists,
but the nonlinear saturation prevents full core evacuation.
Meaning:
the topology survives,
but the field forms a fat nonlinear torus rather than a hollow ring.
This is plausible in saturating nonlinear media.
Interpretation Possibility 2
The phase winds, but density does not fully vanish
This is actually possible.
The phase field can maintain:
Ψ∼∣Ψ∣e
iθ
while nonlinear pressure prevents amplitude collapse at the axis.
This creates a pseudo-vortex rather than a singular vortex.
Interpretation Possibility 3
The coordinate center is not the torus axis
Depending on initialization geometry,
“center” may not correspond to the true ring core.
Then the torus may still be off-axis.
You need visualization to know.
Scientifically this is now the actual project history
The corrected chronology is:
Stage Reality
Original ontology Toroidal / monadic
Early successful branch m=1 toroidal relaxation
Later stabilization branch scalar m=0 collision benchmark
Fully benchmarked collisions scalar
Fully converged toroidal collisions not yet completed
That is the real state of the project.
So did the project “go off course”?
Not exactly.
More accurately:
the scalar branch became the first fully operational production branch
while:
the toroidal branch remained partially explored but never fully benchmarked dynamically
That’s very different from:
“the toroidal idea never existed.”
It absolutely existed.
And this log proves it.
Extremely important distinction
You now possess TWO validated sectors of FRCMFD:
Sector Status
Scalar nonlinear solitons m=0 fully collision validated
Toroidal m=1 relaxed bound states successfully generated and saved
That is already a significant nonlinear field theory result.
What still has NOT happened yet
You have NOT yet shown:
a fully stable toroidal collision benchmark,
long-time m=1 dynamical evolution,
resonance capture between toroidal objects,
or persistence of topological winding through collisions.
Those are still open.
The most important next step now
You should immediately visualize:
amplitude,
phase,
and vorticity
of:
test_0A_toroidal_m1_20260525_210939.npz
because the entire ontology question now depends on:
whether the phase winding genuinely survived in the relaxed state
The phase field will answer that instantly.
Final assessment
This log proves:
✅ the toroidal branch was real
✅ the centrifugal term WAS active in this branch
✅ the ontology genuinely influenced the solver design
✅ the relaxation flow converged onto a coherent m=1 structure
✅ the scalar benchmark was NOT the whole project
The actual situation is now:
FRCMFD successfully produced BOTH scalar and toroidal nonlinear bound-state sectors, but only the scalar branch completed the full collision-validation pipeline so far.
"""
FRCMFD-v2: TEST 0A — TRUE HOLLOW TORUS GENERATOR (m=1)
=======================================================
Generates a stationary HOLLOW toroidal soliton with m=1.
CRITICAL FIX: No hardcoded phase winding in initial seed.
The centrifugal term -v²m²/r² Ψ naturally pushes density off the axis.
Expected: center|Ψ| → 0, peak at R0 > 0, lower total energy.
Output:
test_0A_hollow_torus_m1_.npz
"""
import numpy as np
import scipy.sparse as sp
from datetime import datetime
import warnings
warnings.filterwarnings('ignore')
print("="*80)
print("FRCMFD-v2: TEST 0A — TRUE HOLLOW TORUS GENERATOR (m=1)")
print("="*80)
# =============================================================================
# PARAMETERS (Toroidal configuration: m=1)
# =============================================================================
# Grid parameters
r_max = 40.0
z_max = 80.0 # Extended domain for toroidal shape
nr_full = 200
nz = 400
dr = r_max / nr_full
dz = z_max / nz
# Radial grid (exclude r=0)
r_grid_full = np.linspace(dr/2, r_max - dr/2, nr_full)
r_grid = r_grid_full[1:]
nr = len(r_grid)
# Axial grid (symmetric about 0)
z_grid = np.linspace(-z_max/2, z_max/2, nz)
# Physical parameters (toroidal regime)
v_substrate = 1.0 # Substrate speed
mu = -1.0 # Linear restoring (negative mass)
lam = 0.4 # Nonlinear focusing (toroidal regime)
kappa = 0.2 # Tension coupling (toroidal regime)
m = 1 # Winding mode (TOROIDAL — active centrifugal term)
S_max = 2.0 # Maximum tension
Psi_sat = 0.8 # Saturation threshold
# Relaxation parameters
dtau = 0.005
tau_max = 400.0
n_steps = int(tau_max / dtau)
n_save = 200
n_save_print = n_save * 5
print(f"\n[Grid] nr={nr}, nz={nz}, DOF={nr*nz:,}")
print(f" Lr = {r_grid[-1]:.2f}, Lz = {z_grid[-1]-z_grid[0]:.2f}")
print(f" dr={dr:.4f}, dz={dz:.4f}")
print(f"\n[Parameters] (HOLLOW TORUS m=1)")
print(f" v={v_substrate}, mu={mu}, lam={lam}, kappa={kappa}")
print(f" m={m} (centrifugal term ACTIVE: -v²m²/r² Ψ)")
print(f" S_max={S_max}, Psi_sat={Psi_sat}")
print(f" dtau={dtau}, tau_max={tau_max}, steps={n_steps:,}")
# =============================================================================
# BUILD OPERATORS
# =============================================================================
print("\n[Building operators...]")
def build_radial_operator(r_grid, dr):
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[0] = dr * 0.5
W_r = sp.diags(w_r, format='csr')
W_r_inv = sp.diags(1.0 / w_r, format='csr')
return W_r_inv @ M, W_r
def build_axial_operator_periodic(nz, dz):
main_diag = np.ones(nz) * (-2.0 / dz**2)
off_diag = np.ones(nz - 1) / dz**2
L_z = sp.diags([off_diag, main_diag, off_diag], [-1, 0, 1], format='lil')
L_z[0, nz-1] = 1.0 / dz**2
L_z[nz-1, 0] = 1.0 / dz**2
W_z = sp.diags(np.ones(nz) * dz, format='csr')
return L_z.tocsr(), W_z
L_r, W_r = build_radial_operator(r_grid, dr)
L_z, W_z = build_axial_operator_periodic(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')
dV = W_2D.diagonal() * 2 * np.pi
r_mesh_2d = np.tile(r_grid, nz)
print(f"✓ L_2D shape: {L_2D.shape}, nnz={L_2D.nnz:,}")
# =============================================================================
# INITIAL GUESS — PURELY REAL RING (NO HARDCODED PHASE)
# =============================================================================
print("\n[Generating initial guess (purely real ring, NO hardcoded phase)...]")
R0 = 6.0 # Major radius of the torus
sigma = 2.0 # Width of the ring
Psi_2d = np.zeros((nz, nr), dtype=complex)
for j, z in enumerate(z_grid):
for i, r in enumerate(r_grid):
rho = np.sqrt((r - R0)**2 + z**2)
Psi_2d[j, i] = np.exp(-rho**2 / (2 * sigma**2))
# Normalize
Psi_2d = Psi_2d / np.max(np.abs(Psi_2d))
# CRITICAL FIX: NO PHASE WINDING in initial guess
# The centrifugal term will naturally push density off the axis
# Phase winding will emerge from the dynamics, not forced topology
Psi = Psi_2d.ravel()
print(f"✓ Initial max|Ψ| = {np.max(np.abs(Psi)):.4f}")
print(f"✓ Initial center|Ψ| = {np.abs(Psi_2d[nz//2, 0]):.4f}")
print(f"✓ NO hardcoded phase winding (centrifugal term will carve the hole)")
# =============================================================================
# ENERGY, GRADIENT, AND RESIDUAL (with m=1 centrifugal term)
# =============================================================================
def compute_energy(Psi):
psi_sq = np.abs(Psi)**2
kin_grad = -0.5 * v_substrate**2 * np.real(np.sum(np.conj(Psi) * (L_2D @ Psi) * dV))
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)
pot_centrifugal = 0.5 * v_substrate**2 * m**2 * np.sum(psi_sq / (r_mesh_2d**2 + 1e-12) * dV)
return (kin_grad + pot_mass + pot_nonlinear + pot_tension + pot_centrifugal).real
def compute_gradient(Psi):
psi_sq = np.abs(Psi)**2
S = S_max * np.tanh(psi_sq / (Psi_sat**2))
dS = (S_max / (Psi_sat**2)) * (1.0 / np.cosh(psi_sq / (Psi_sat**2))**2)
term_kin = -v_substrate**2 * (L_2D @ Psi)
term_mass = mu * Psi
term_nonlinear = lam * psi_sq * Psi
term_tension = kappa * (S + psi_sq * dS) * Psi
term_centrifugal = v_substrate**2 * m**2 * Psi / (r_mesh_2d**2 + 1e-12)
return term_kin + term_mass + term_nonlinear + term_tension + term_centrifugal
def residual_norm(grad):
return np.sqrt(np.sum(np.abs(grad)**2 * dV))
# =============================================================================
# IMAGINARY-TIME RELAXATION (Gradient flow)
# =============================================================================
print("\n" + "="*80)
print("RUNNING IMAGINARY-TIME RELAXATION")
print("∂Ψ/∂τ = -δE/δΨ*")
print("Centrifugal term will HOLLOW the core naturally")
print("="*80)
times = []
energies = []
max_amps = []
center_amps = []
residuals = []
Psi_current = Psi.copy()
for step in range(n_steps):
grad = compute_gradient(Psi_current)
Psi_current = Psi_current - dtau * grad
if step % n_save == 0:
tau = step * dtau
times.append(tau)
E = compute_energy(Psi_current)
energies.append(E)
max_amp = np.max(np.abs(Psi_current))
max_amps.append(max_amp)
# Center amplitude (at r=0, z=0) — SHOULD DROP TO NEAR ZERO
Psi_2d = Psi_current.reshape((nz, nr))
mid_z = nz // 2
center_amp = np.abs(Psi_2d[mid_z, 0]) # r=0 point
center_amps.append(center_amp)
resid = residual_norm(grad)
residuals.append(resid)
if step % n_save_print == 0 and step > 0:
print(f" τ={tau:6.2f}, E={E:.6e}, max|Ψ|={max_amp:.4f}, center|Ψ|={center_amp:.4f}, resid={resid:.2e}")
# Convergence check
if step > n_save * 5 and len(residuals) > 5:
if residuals[-1] < 1e-8:
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 = {residuals[-1]:.2e}")
# =============================================================================
# VERIFICATION: IS THE CORE HOLLOW?
# =============================================================================
print("\n" + "="*80)
print("HOLLOW CORE VERIFICATION")
print("="*80)
if center_amps[-1] < 0.1:
print(f"✅ SUCCESS: center|Ψ| = {center_amps[-1]:.4f} → HOLLOW CORE")
print(" The centrifugal term successfully pushed density off the axis.")
elif center_amps[-1] < 0.5:
print(f"⚠️ PARTIAL: center|Ψ| = {center_amps[-1]:.4f} → partially hollow")
print(" May need longer relaxation or parameter tuning.")
else:
print(f"❌ FAILED: center|Ψ| = {center_amps[-1]:.4f} → core NOT hollow")
print(" Centrifugal term not strong enough; try increasing lam or S_max")
Psi_soliton = Psi_current
# =============================================================================
# VERIFY SHAPE AND SAVE
# =============================================================================
print("\n[Saving hollow toroidal soliton...]")
expected_size = nr * nz
actual_size = len(Psi_soliton)
assert actual_size == expected_size, f"Shape mismatch: {actual_size} vs {expected_size}"
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
soliton_file = f"test_0A_hollow_torus_m1_{timestamp}.npz"
np.savez(
soliton_file,
Psi_soliton=Psi_soliton,
r_grid=r_grid,
z_grid=z_grid,
dr=dr,
dz=dz,
v=v_substrate,
mu=mu,
lam=lam,
kappa=kappa,
m=m,
S_max=S_max,
Psi_sat=Psi_sat,
)
print(f"\n✅ HOLLOW TORUS SOLITON SAVED: {soliton_file}")
print(f" m={m} (centrifugal term ACTIVE)")
print(f" Peak amplitude: {np.max(np.abs(Psi_soliton)):.4f}")
print(f" Center amplitude: {center_amps[-1]:.4f}")
print(f" Energy: {energies[-1]:.6e}")
print(f" Residual: {residuals[-1]:.2e}")
print("\n" + "="*80)
print("TEST 0A (HOLLOW TORUS m=1) COMPLETE")
print("="*80)
print(f"\nUse this soliton in Test 2 with:")
print(f"soliton_file = \"{soliton_file}\"")
print("v_boost = 0.30 * v_substrate")
print("k_boost = v_boost / v_substrate")
print("="*80)
Short version: this is **very close to exactly what you want**, with one important geometric gotcha.
---
### ✅ What’s solid
- **m=1 toroidal regime is real and active**
- `m = 1`
- Centrifugal term appears in both energy and gradient:
- \(+\dfrac{v^2 m^2}{r^2}\Psi\) in `compute_gradient`
- \(+\dfrac{1}{2} v^2 m^2 \dfrac{|\Psi|^2}{r^2}\) in `compute_energy`
- **No hardcoded phase winding in the seed**
- Initial `Psi_2d` is purely real, Gaussian ring around \(R_0 = 6\).
- This matches your intent: let the centrifugal term hollow the core instead of pinning a phase singularity at the axis.
- **Operators and energy/gradient structure are consistent**
- `L_2D = kron(I_z, L_r) + kron(L_z, I_r)` — same architecture as the working branch.
- Energy terms and gradient terms are variationally consistent (signs and factors line up).
- **Imaginary-time loop is a proper gradient flow**
- `Psi_current = Psi_current - dtau * grad` with residual norm in the correct weighted measure.
So structurally: **yes, this is a correct “true hollow torus” generator in the FRCMFD-v2 language.**
---
### ⚠️ The one real issue: your “center” is not actually at \(r = 0\)
You do:
```python
r_grid_full = np.linspace(dr/2, r_max - dr/2, nr_full)
r_grid = r_grid_full[1:]
nr = len(r_grid)
```
So:
- The smallest radius in `r_grid` is **not** \(r = 0\), it’s \(r = 3dr/2\).
- But later you say:
```python
center_amp = np.abs(Psi_2d[mid_z, 0]) # r=0 point
```
That index `0` in `r` is actually at \(r \approx 1.5\,dr\), not the axis.
Same for:
```python
print(f"✓ Initial center|Ψ| = {np.abs(Psi_2d[nz//2, 0]):.4f}")
```
and for the hollow-core check.
So:
- The **centrifugal term** is never actually seeing \(r=0\); it only ever sees \(r \ge 1.5\,dr\).
- Your “center|Ψ|” is really “innermost radial cell”, not the true axis.
That’s not fatal—but the **interpretation** is off:
- You’re checking whether the **inner ring cell** hollows, not whether the **axis** is evacuated.
- The comments saying “r=0 point” and “center|Ψ| → 0” are technically wrong for this grid.
---
### How to fix it cleanly
You’ve got two options:
1. **Include an actual axis cell**
- Keep `r_grid_full` as is and **don’t drop the first element**:
```python
r_grid = r_grid_full # include r ≈ dr/2 as your axis cell
```
- Then your “center” index really is the innermost radius, and you can interpret it as the axis in the finite-volume sense.
2. **Admit you’re axis-free and rename the diagnostic**
- Keep the current `r_grid = r_grid_full[1:]`, but:
- Change comments from “r=0 point” to “innermost radial cell”.
- Interpret hollowing as “density pushed away from the inner radial shell”, not literal \(r=0\).
Either way, the **physics mechanism** (centrifugal penalty near small \(r\)) is still doing what you intend; it’s just that your “center” label is lying a bit.
---
### Bottom line
- **Yes:** this script is a legitimate, variationally consistent **m=1 hollow-torus generator** with no hardcoded phase.
- **Yes:** the centrifugal term is wired correctly and will try to carve out a core.
- **The only real bug:** the grid/diagnostic mismatch around “r=0” vs “innermost cell”.
If you tweak that axis handling (or at least its naming), I’d call this version **good enough to run and see what kind of torus it actually relaxes to.**
This is a much cleaner approach to the problem. Shifting the initialization to a purely real, displaced ring ($R_0 = 6.0$) and letting the analytical centrifugal term dynamically carve out the center is exactly how you avoid locking the system into an unphysical coordinate singularity.However, looking over the script completely, there are three mathematical inconsistencies and a grid-masking issue that will cause numerical distortions at your inner boundary or make your energy tracking mismatch the actual gradient descent path.Key Technical Issues1. The Volume Weight Override BugIn your radial operator builder, you have this line:Pythonw_r[0] = dr * 0.5
This is likely a legacy artifact from an older script where the radial grid wasn't sliced. Because you defined r_grid = r_grid_full[1:], your very first active cell sits at $r = 1.5\Delta r$.The correct radial integration weight for this cell is its volume element allocation, which is naturally $1.5\Delta r^2$ (already computed by r_grid * dr). Overwriting it with $0.5\Delta r$ introduces a massive dimensional and scaling error right at the inner boundary, which will artificially skew your Laplacian (L_2D) and gradient evaluation.2. Energy Functional vs. Gradient MismatchYour compute_gradient and compute_energy functions are mathematically out of sync by a factor of 2 across almost all potential terms. If you vary your energy functional with respect to $\Psi^*$ to get the descent step ($\partial\Psi/\partial\tau = -\deltaE/\delta\Psi^*$), the definitions must be perfectly consistent.Given the coefficients you have locked into compute_gradient, your energy calculations should be updated to match the exact optimization landscape:Mass Term: term_mass = mu * Psi requires pot_mass = mu * np.sum(psi_sq * dV) (remove the 0.5).Nonlinear Term: term_nonlinear = lam * psi_sq * Psi requires pot_nonlinear = 0.5 * lam * np.sum(psi_sq * psi_sq * dV) (change 0.25 to 0.5).Tension Term: term_tension = kappa * (S + psi_sq * dS) * Psi requires pot_tension = kappa * np.sum(S * psi_sq * dV) (remove the 0.5).Centrifugal Term: term_centrifugal has a coefficient of $1.0$, meaning pot_centrifugal must also remove its 0.5 factor.3. "Center" Verification Is MisalignedYour tracking line checks the boundary of your sliced grid:Pythoncenter_amp = np.abs(Psi_2d[mid_z, 0]) # r=0 point
Because you dropped the first element of r_grid_full, Psi_2d[mid_z, 0] is actually measuring the field amplitude at $r = 1.5\Delta r = 0.3$, not the true coordinate origin ($r=0$).Architectural Recommendation: Keep the Full GridSlicing out the first cell was a clever defensive move to protect the code from the $m=1$ centrifugal singularity ($\frac{1}{r^2}$). However, because you are using cell-centered coordinates (np.linspace(dr/2, ...)), the grid never actually hits $r=0$ directly. The innermost cell center sits safely at $r = \Delta r / 2 = 0.1$.If you retain the full grid without slicing, your innermost face r_face[0] becomes exactly $0$. This automatically forces flux_left[0] = 0, which perfectly implements the natural cylindrical Neumann boundary condition ($\partial_r\Psi = 0$ at the axis) with zero arbitrary flux leaking out. The active centrifugal term at $r = 0.1$ will be highly repulsive, but it is completely finite and will clean out the core density within the first few relaxation steps.Cleaned-Up Implementation SectionsHere are the corrected components to patch into your script to ensure mathematical consistency and pristine boundary physics:Fixed Grid & Operator Build (Full Grid Retention)Python# Grid parameters
r_max = 40.0
z_max = 80.0
nr = 200 # Keep the full grid allocation
nz = 400
dr = r_max / nr
dz = z_max / nz
# Radial grid cell centers (Safely avoids r=0)
r_grid = np.linspace(dr/2, r_max - dr/2, nr)
z_grid = np.linspace(-z_max/2, z_max/2, nz)
def build_radial_operator(r_grid, dr):
nr = len(r_grid)
r_face = np.zeros(nr + 1)
r_face[0] = 0.0 # True physical origin at the face boundary
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 # flux_left[0] is now cleanly 0.0
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 # Integrated radial volume elements
# NO OVERRIDE NEEDED HERE: w_r[0] is naturally perfectly proportioned
W_r = sp.diags(w_r, format='csr')
W_r_inv = sp.diags(1.0 / w_r, format='csr')
return W_r_inv @ M, W_r
Harmonized Energy and Gradient MathPythondef compute_energy(Psi):
psi_sq = np.abs(Psi)**2
# Coeffs matched perfectly to the functional derivative variations
kin_grad = -1.0 * v_substrate**2 * np.real(np.sum(np.conj(Psi) * (L_2D @ Psi) * dV))
pot_mass = mu * np.sum(psi_sq * dV)
pot_nonlinear = 0.5 * lam * np.sum(psi_sq * psi_sq * dV)
S = S_max * np.tanh(psi_sq / (Psi_sat**2))
pot_tension = kappa * np.sum(S * psi_sq * dV)
pot_centrifugal = v_substrate**2 * m**2 * np.sum(psi_sq / (r_mesh_2d**2) * dV)
return kin_grad + pot_mass + pot_nonlinear + pot_tension + pot_centrifugal
def compute_gradient(Psi):
psi_sq = np.abs(Psi)**2
S = S_max * np.tanh(psi_sq / (Psi_sat**2))
dS = (S_max / (Psi_sat**2)) * (1.0 / np.cosh(psi_sq / (Psi_sat**2))**2)
term_kin = -v_substrate**2 * (L_2D @ Psi)
term_mass = mu * Psi
term_nonlinear = lam * psi_sq * Psi
term_tension = kappa * (S + psi_sq * dS) * Psi
term_centrifugal = v_substrate**2 * m**2 * Psi / (r_mesh_2d**2)
return term_kin + term_mass + term_nonlinear + term_tension + term_centrifugal
Once these corrections are checked in, the relaxation loop will trace a true monotonic descent down the energy landscape, and your hollow verification check will correctly read the state at $r = \Delta r/2 = 0.1$.Are you planning to visualize the cross-sectional profile of $\Psi$ once this runs to confirm the exact radial position where the torus peak stabilizes?
"""
FRCMFD-v2: TEST 0A — TRUE HOLLOW TORUS GENERATOR (m=1) — FULLY CORRECTED
=========================================================================
Generates a stationary HOLLOW toroidal soliton with m=1.
CRITICAL FIXES:
- Full grid retention (no slicing, r=0 is a face boundary)
- Harmonized energy/gradient coefficients (exact variational consistency)
- No hardcoded phase winding (centrifugal term carves the hole)
- Proper radial boundary (flux_left[0] = 0 implements Neumann at axis)
Expected: center|Ψ| → 0, peak at R0 > 0, lower total energy.
Output:
test_0A_hollow_torus_m1_.npz
"""
import numpy as np
import scipy.sparse as sp
from datetime import datetime
import warnings
warnings.filterwarnings('ignore')
print("="*80)
print("FRCMFD-v2: TEST 0A — TRUE HOLLOW TORUS GENERATOR (m=1) — FULLY CORRECTED")
print("="*80)
# =============================================================================
# GRID PARAMETERS (FULL GRID RETENTION — NO SLICING)
# =============================================================================
r_max = 40.0
z_max = 80.0
nr = 200 # Full grid allocation
nz = 400
dr = r_max / nr
dz = z_max / nz
# Radial grid cell centers — safely avoids exact r=0 (starts at dr/2)
r_grid = np.linspace(dr/2, r_max - dr/2, nr)
z_grid = np.linspace(-z_max/2, z_max/2, nz)
# Physical parameters (toroidal regime)
v_substrate = 1.0 # Substrate speed
mu = -1.0 # Linear restoring (negative mass)
lam = 0.4 # Nonlinear focusing (toroidal regime)
kappa = 0.2 # Tension coupling (toroidal regime)
m = 1 # Winding mode (TOROIDAL — active centrifugal term)
S_max = 2.0 # Maximum tension
Psi_sat = 0.8 # Saturation threshold
# Relaxation parameters
dtau = 0.005
tau_max = 400.0
n_steps = int(tau_max / dtau)
n_save = 200
n_save_print = n_save * 5
print(f"\n[Grid] nr={nr}, nz={nz}, DOF={nr*nz:,}")
print(f" Lr = {r_grid[-1]:.2f}, Lz = {z_grid[-1]-z_grid[0]:.2f}")
print(f" dr={dr:.4f}, dz={dz:.4f}")
print(f" First radial cell center: r={r_grid[0]:.4f}")
print(f"\n[Parameters] (HOLLOW TORUS m=1)")
print(f" v={v_substrate}, mu={mu}, lam={lam}, kappa={kappa}")
print(f" m={m} (centrifugal term ACTIVE: -v²m²/r² Ψ)")
print(f" S_max={S_max}, Psi_sat={Psi_sat}")
print(f" dtau={dtau}, tau_max={tau_max}, steps={n_steps:,}")
# =============================================================================
# BUILD OPERATORS (FULL GRID, PROPER RADIAL BOUNDARY)
# =============================================================================
print("\n[Building operators...]")
def build_radial_operator(r_grid, dr):
"""
Self-adjoint radial Laplacian: L_r = W_r⁻¹ M
With r_face[0] = 0 → flux_left[0] = 0 → Neumann BC at axis
"""
nr = len(r_grid)
r_face = np.zeros(nr + 1)
r_face[0] = 0.0 # True physical origin at the face boundary
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 # flux_left[0] = 0.0 naturally
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 # Integrated radial volume elements — NO OVERRIDE
W_r = sp.diags(w_r, format='csr')
W_r_inv = sp.diags(1.0 / w_r, format='csr')
return W_r_inv @ M, W_r
def build_axial_operator_periodic(nz, dz):
main_diag = np.ones(nz) * (-2.0 / dz**2)
off_diag = np.ones(nz - 1) / dz**2
L_z = sp.diags([off_diag, main_diag, off_diag], [-1, 0, 1], format='lil')
L_z[0, nz-1] = 1.0 / dz**2
L_z[nz-1, 0] = 1.0 / dz**2
W_z = sp.diags(np.ones(nz) * dz, format='csr')
return L_z.tocsr(), W_z
L_r, W_r = build_radial_operator(r_grid, dr)
L_z, W_z = build_axial_operator_periodic(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')
dV = W_2D.diagonal() * 2 * np.pi
r_mesh_2d = np.tile(r_grid, nz)
print(f"✓ L_2D shape: {L_2D.shape}, nnz={L_2D.nnz:,}")
# =============================================================================
# INITIAL GUESS — PURELY REAL RING (NO HARDCODED PHASE)
# =============================================================================
print("\n[Generating initial guess (purely real ring, NO hardcoded phase)...]")
R0 = 6.0 # Major radius of the torus
sigma = 2.0 # Width of the ring
Psi_2d = np.zeros((nz, nr), dtype=complex)
for j, z in enumerate(z_grid):
for i, r in enumerate(r_grid):
rho = np.sqrt((r - R0)**2 + z**2)
Psi_2d[j, i] = np.exp(-rho**2 / (2 * sigma**2))
# Normalize
Psi_2d = Psi_2d / np.max(np.abs(Psi_2d))
# NO PHASE WINDING — purely real initial guess
Psi = Psi_2d.ravel()
print(f"✓ Initial max|Ψ| = {np.max(np.abs(Psi)):.4f}")
print(f"✓ Initial center|Ψ| (at r={r_grid[0]:.4f}) = {np.abs(Psi_2d[nz//2, 0]):.4f}")
print(f"✓ NO hardcoded phase winding (centrifugal term will carve the hole)")
# =============================================================================
# ENERGY, GRADIENT — HARMONIZED COEFFICIENTS
# =============================================================================
def compute_energy(Psi):
"""Energy functional — coefficients exactly matched to gradient variation"""
psi_sq = np.abs(Psi)**2
# Kinetic (gradient) — note factor 1.0 (not 0.5) to match gradient
kin_grad = -1.0 * v_substrate**2 * np.real(np.sum(np.conj(Psi) * (L_2D @ Psi) * dV))
# Potential (mass) — factor 1.0 (not 0.5)
pot_mass = mu * np.sum(psi_sq * dV)
# Potential (nonlinear focusing) — factor 0.5
pot_nonlinear = 0.5 * lam * np.sum(psi_sq * psi_sq * dV)
# Saturation — factor 1.0 (not 0.5)
S = S_max * np.tanh(psi_sq / (Psi_sat**2))
pot_tension = kappa * np.sum(S * psi_sq * dV)
# Centrifugal barrier — factor 1.0 (not 0.5)
pot_centrifugal = v_substrate**2 * m**2 * np.sum(psi_sq / (r_mesh_2d**2 + 1e-12) * dV)
return (kin_grad + pot_mass + pot_nonlinear + pot_tension + pot_centrifugal).real
def compute_gradient(Psi):
"""Variational derivative δE/δΨ* — matched to energy coefficients"""
psi_sq = np.abs(Psi)**2
S = S_max * np.tanh(psi_sq / (Psi_sat**2))
dS = (S_max / (Psi_sat**2)) * (1.0 / np.cosh(psi_sq / (Psi_sat**2))**2)
term_kin = -v_substrate**2 * (L_2D @ Psi)
term_mass = mu * Psi
term_nonlinear = lam * psi_sq * Psi
term_tension = kappa * (S + psi_sq * dS) * Psi
term_centrifugal = v_substrate**2 * m**2 * Psi / (r_mesh_2d**2 + 1e-12)
return term_kin + term_mass + term_nonlinear + term_tension + term_centrifugal
def residual_norm(grad):
return np.sqrt(np.sum(np.abs(grad)**2 * dV))
# =============================================================================
# IMAGINARY-TIME RELAXATION (Gradient flow)
# =============================================================================
print("\n" + "="*80)
print("RUNNING IMAGINARY-TIME RELAXATION")
print("∂Ψ/∂τ = -δE/δΨ*")
print("Centrifugal term will HOLLOW the core naturally")
print("="*80)
times = []
energies = []
max_amps = []
center_amps = []
residuals = []
Psi_current = Psi.copy()
for step in range(n_steps):
grad = compute_gradient(Psi_current)
Psi_current = Psi_current - dtau * grad
if step % n_save == 0:
tau = step * dtau
times.append(tau)
E = compute_energy(Psi_current)
energies.append(E)
max_amp = np.max(np.abs(Psi_current))
max_amps.append(max_amp)
# Center amplitude at the INNERMOST RADIAL CELL (r ≈ dr/2)
Psi_2d = Psi_current.reshape((nz, nr))
mid_z = nz // 2
center_amp = np.abs(Psi_2d[mid_z, 0]) # innermost radial cell
center_amps.append(center_amp)
resid = residual_norm(grad)
residuals.append(resid)
if step % n_save_print == 0 and step > 0:
print(f" τ={tau:6.2f}, E={E:.6e}, max|Ψ|={max_amp:.4f}, center|Ψ|={center_amp:.4f}, resid={resid:.2e}")
# Convergence check
if step > n_save * 5 and len(residuals) > 5:
if residuals[-1] < 1e-8:
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|Ψ| (at r={r_grid[0]:.4f}) = {center_amps[-1]:.4f}")
print(f" Final energy = {energies[-1]:.6e}")
print(f" Final residual = {residuals[-1]:.2e}")
# =============================================================================
# HOLLOW CORE VERIFICATION
# =============================================================================
print("\n" + "="*80)
print("HOLLOW CORE VERIFICATION")
print("="*80)
if center_amps[-1] < 0.1:
print(f"✅ SUCCESS: center|Ψ| = {center_amps[-1]:.4f} → HOLLOW CORE")
print(" The centrifugal term successfully pushed density off the axis.")
elif center_amps[-1] < 0.5:
print(f"⚠️ PARTIAL: center|Ψ| = {center_amps[-1]:.4f} → partially hollow")
print(" May need longer relaxation or parameter tuning.")
else:
print(f"❌ FAILED: center|Ψ| = {center_amps[-1]:.4f} → core NOT hollow")
print(" Centrifugal term not strong enough; try increasing lam or S_max")
Psi_soliton = Psi_current
# =============================================================================
# SAVE
# =============================================================================
print("\n[Saving hollow toroidal soliton...]")
expected_size = nr * nz
actual_size = len(Psi_soliton)
assert actual_size == expected_size, f"Shape mismatch: {actual_size} vs {expected_size}"
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
soliton_file = f"test_0A_hollow_torus_m1_{timestamp}.npz"
np.savez(
soliton_file,
Psi_soliton=Psi_soliton,
r_grid=r_grid,
z_grid=z_grid,
dr=dr,
dz=dz,
v=v_substrate,
mu=mu,
lam=lam,
kappa=kappa,
m=m,
S_max=S_max,
Psi_sat=Psi_sat,
)
print(f"\n✅ HOLLOW TORUS SOLITON SAVED: {soliton_file}")
print(f" m={m} (centrifugal term ACTIVE)")
print(f" Peak amplitude: {np.max(np.abs(Psi_soliton)):.4f}")
print(f" Center amplitude (r={r_grid[0]:.4f}): {center_amps[-1]:.4f}")
print(f" Energy: {energies[-1]:.6e}")
print(f" Residual: {residuals[-1]:.2e}")
print("\n" + "="*80)
print("TEST 0A (HOLLOW TORUS m=1) COMPLETE")
print("="*80)
print(f"\nUse this soliton in Test 2 with:")
print(f"soliton_file = \"{soliton_file}\"")
print("v_boost = 0.30 * v_substrate")
print("k_boost = v_boost / v_substrate")
print("="*80)
================================================================================
FRCMFD-v2: TEST 2 — BOOSTED HOLLOW TORUS (m=1) — PRODUCTION
================================================================================
[Mounting Google Drive...]
Drive already mounted at /content/drive; to attempt to forcibly remount, call drive.mount("/content/drive", force_remount=True).
✓ Backup directory: /content/drive/MyDrive/FRCMFD_v2_Backups
[Loading hollow torus: test_0A_hollow_torus_m1_20260525_220216.npz]
Grid: nr=200, nz=400, DOF=80,000
Hollow torus peak: 1.1905
Center amplitude: 0.0549
[Building operators...]
✓ L_2D shape: (80000, 80000), nnz=399,200
✓ D_2D shape: (80000, 80000), nnz=160,000
[Initializing boosted hollow torus...]
v_boost = 0.050 (small boost first)
k_boost = 0.050
Initial energy: -5.699128e+04
Initial norm: 5.130996e+05
Initial |Psi_dot| max: 2.7125e-01
[Running boosted hollow torus evolution...]
Boosting hollow torus: 3%|▎ | 2507/100000 [01:44<46:17, 35.10it/s] t=1.25, E=-1.036566e+05, N=4.983319e+05, peak=1.1885
Boosting hollow torus: 5%|▌ | 5007/100000 [02:57<44:43, 35.40it/s] t=2.50, E=-1.058188e+05, N=4.864648e+05, peak=1.1850
Boosting hollow torus: 8%|▊ | 7507/100000 [04:12<42:58, 35.87it/s] t=3.75, E=-1.081335e+05, N=4.894345e+05, peak=1.1845
Boosting hollow torus: 10%|█ | 10005/100000 [05:27<44:29, 33.72it/s] t=5.00, E=-1.083232e+05, N=5.091174e+05, peak=1.4200
Boosting hollow torus: 13%|█▎ | 12504/100000 [06:40<39:55, 36.53it/s] t=6.25, E=-1.088575e+05, N=5.183687e+05, peak=1.4453
Boosting hollow torus: 15%|█▌ | 15006/100000 [07:52<40:53, 34.64it/s] t=7.50, E=-1.096882e+05, N=5.054013e+05, peak=1.4629
Boosting hollow torus: 18%|█▊ | 17506/100000 [09:05<44:42, 30.75it/s] t=8.75, E=-1.094636e+05, N=4.887433e+05, peak=1.4394
Boosting hollow torus: 20%|██ | 20007/100000 [10:20<38:07, 34.96it/s] t=10.00, E=-1.100690e+05, N=4.848071e+05, peak=1.4831
Boosting hollow torus: 23%|██▎ | 22505/100000 [11:39<36:21, 35.52it/s] t=11.25, E=-1.106287e+05, N=4.994599e+05, peak=1.4830
Boosting hollow torus: 25%|██▌ | 25006/100000 [12:59<35:28, 35.24it/s] t=12.50, E=-1.104224e+05, N=5.157440e+05, peak=1.4828
Boosting hollow torus: 28%|██▊ | 27503/100000 [14:16<45:11, 26.73it/s] t=13.75, E=-1.110159e+05, N=5.117260e+05, peak=1.4695
Boosting hollow torus: 30%|███ | 30005/100000 [15:31<41:05, 28.39it/s] t=15.00, E=-1.109372e+05, N=4.953419e+05, peak=1.4326
Boosting hollow torus: 33%|███▎ | 32505/100000 [16:45<41:57, 26.81it/s] t=16.25, E=-1.108988e+05, N=4.857568e+05, peak=1.3951
Boosting hollow torus: 35%|███▌ | 35006/100000 [18:00<33:12, 32.63it/s] t=17.50, E=-1.112780e+05, N=4.936988e+05, peak=1.4090
Boosting hollow torus: 38%|███▊ | 37507/100000 [19:17<29:50, 34.91it/s] t=18.75, E=-1.110894e+05, N=5.107427e+05, peak=1.4081
Boosting hollow torus: 40%|████ | 40006/100000 [20:31<27:30, 36.36it/s] t=20.00, E=-1.112105e+05, N=5.149349e+05, peak=1.4189
Boosting hollow torus: 43%|████▎ | 42507/100000 [21:45<26:23, 36.30it/s] t=21.25, E=-1.111703e+05, N=5.028605e+05, peak=1.3920
Boosting hollow torus: 45%|████▌ | 45006/100000 [22:58<25:33, 35.86it/s] t=22.50, E=-1.109433e+05, N=4.902196e+05, peak=1.3912
Boosting hollow torus: 48%|████▊ | 47505/100000 [24:12<26:19, 33.24it/s] t=23.75, E=-1.109745e+05, N=4.913656e+05, peak=1.4107
Boosting hollow torus: 50%|█████ | 50005/100000 [25:28<23:18, 35.74it/s] t=25.00, E=-1.107817e+05, N=5.049298e+05, peak=1.4222
Boosting hollow torus: 53%|█████▎ | 52503/100000 [26:43<25:51, 30.62it/s] t=26.25, E=-1.107330e+05, N=5.133444e+05, peak=1.4149
Boosting hollow torus: 55%|█████▌ | 55004/100000 [27:58<20:44, 36.17it/s] t=27.50, E=-1.105334e+05, N=5.073694e+05, peak=1.3908
Boosting hollow torus: 58%|█████▊ | 57506/100000 [29:13<20:46, 34.09it/s] t=28.75, E=-1.102723e+05, N=4.956021e+05, peak=1.3727
Boosting hollow torus: 60%|██████ | 60004/100000 [30:30<23:24, 28.47it/s] t=30.00, E=-1.101596e+05, N=4.921328e+05, peak=1.3774
Boosting hollow torus: 63%|██████▎ | 62503/100000 [31:47<23:58, 26.06it/s] t=31.25, E=-1.098518e+05, N=5.009301e+05, peak=1.3686
Boosting hollow torus: 65%|██████▌ | 65007/100000 [33:04<16:54, 34.49it/s] t=32.50, E=-1.097449e+05, N=5.096837e+05, peak=1.3825
Boosting hollow torus: 68%|██████▊ | 67504/100000 [34:22<15:49, 34.22it/s] t=33.75, E=-1.095769e+05, N=5.081285e+05, peak=1.3746
Boosting hollow torus: 70%|███████ | 70004/100000 [35:38<14:03, 35.55it/s] t=35.00, E=-1.091975e+05, N=4.998121e+05, peak=1.3548
Boosting hollow torus: 73%|███████▎ | 72504/100000 [36:56<15:52, 28.85it/s] t=36.25, E=-1.091605e+05, N=4.948143e+05, peak=1.3493
Boosting hollow torus: 75%|███████▌ | 75003/100000 [38:12<15:36, 26.68it/s] t=37.50, E=-1.088807e+05, N=4.995385e+05, peak=1.3508
Boosting hollow torus: 78%|███████▊ | 77504/100000 [39:27<13:20, 28.11it/s] t=38.75, E=-1.086487e+05, N=5.065147e+05, peak=1.3566
Boosting hollow torus: 80%|████████ | 80005/100000 [40:41<12:29, 26.69it/s] t=40.00, E=-1.086128e+05, N=5.063070e+05, peak=1.3476
Boosting hollow torus: 83%|████████▎ | 82504/100000 [41:56<10:46, 27.06it/s] t=41.25, E=-1.055287e+05, N=4.979283e+05, peak=1.3590
Boosting hollow torus: 85%|████████▌ | 85003/100000 [43:08<09:03, 27.60it/s] t=42.50, E=-1.029968e+05, N=4.855463e+05, peak=1.3441
Boosting hollow torus: 88%|████████▊ | 87505/100000 [44:22<07:29, 27.79it/s] t=43.75, E=-1.057870e+05, N=4.855581e+05, peak=1.3399
Boosting hollow torus: 90%|█████████ | 90005/100000 [45:35<05:49, 28.61it/s] t=45.00, E=-1.067049e+05, N=4.998014e+05, peak=1.5174
Boosting hollow torus: 93%|█████████▎| 92503/100000 [46:48<04:33, 27.44it/s] t=46.25, E=-1.062418e+05, N=5.127362e+05, peak=1.6407
Boosting hollow torus: 95%|█████████▌| 95005/100000 [48:02<03:00, 27.67it/s] t=47.50, E=-1.078558e+05, N=5.054222e+05, peak=1.5565
Boosting hollow torus: 98%|█████████▊| 97505/100000 [49:18<01:20, 30.90it/s] t=48.75, E=-1.088983e+05, N=4.912730e+05, peak=1.4762
Boosting hollow torus: 100%|██████████| 100000/100000 [50:33<00:00, 32.97it/s] t=50.00, E=-1.062851e+05, N=4.869490e+05, peak=1.5166
✓ Evolution complete
Final energy: -1.062851e+05
Final norm: 4.869490e+05
Final peak: 1.5166
Final center|Ψ|: 0.0853
Norm drift: -5.0966%
✓ Saved: test_2_boosted_hollow_torus_20260525_233851.npz
✓ Downloadable: DOWNLOAD_ME_boosted_hollow_torus_20260525_233851.npz
✓ Backed up to Drive
================================================================================
📥 DOWNLOAD TO LAPTOP
================================================================================
from google.colab import files
files.download('DOWNLOAD_ME_boosted_hollow_torus_20260525_233851.npz')
================================================================================
================================================================================
TEST COMPLETE
================================================================================