HomeTutorials05 – Electric Charge
05Intermediate~5 min

Electric Charge

Switch to complex-valued wave fields and discover that the phase of the wave acts as electric charge. No electromagnetism is added — it emerges from wave interference.

What you'll learn

  • How to enable complex fields: field_level=lfm.FieldLevel.COMPLEX
  • How to set the phase of a soliton: phase=0.0 or phase=np.pi
  • The interference mechanism: constructive (repel) vs destructive (attract)
  • Why charge is a property of the wave, not a separate quantity

Which equation is running?

LevelFieldForces includedSpeed
REALΨ ∈ ℝGravity onlyTutorials 01–04
COMPLEX ←Ψ ∈ ℂGravity + EMThis tutorial (~2× slower)
FULLΨₐ ∈ ℂ³All 4 forces~6× slower

Phase requires two components: psi_real and psi_imag. A real-valued field carries no phase and therefore no charge — it only creates gravity. FULL is always physically correct; COMPLEX is the minimum level needed for electromagnetism. Tutorials 09–11 (atoms and molecules) use COMPLEX or FULL.

Charge as phase — the mechanism

When two complex waves overlap, the total energy density is:

|Ψ₁ + Ψ₂|² = |Ψ₁|² + |Ψ₂|² + 2Re(Ψ₁* Ψ₂)

The cross-term 2Re(Ψ₁*Ψ₂) depends on relative phase Δθ:

Same phase (Δθ = 0)
+2|Ψ₁||Ψ₂| → energy UP → REPEL
Opposite phase (Δθ = π)
−2|Ψ₁||Ψ₂| → energy DOWN → ATTRACT

GOV-02 then lowers χ where |Ψ|² is high. More energy = deeper well = stronger attraction. This is Coulomb's law without Coulomb's law.

Full script

"""05 – Electric Charge

So far we've used real-valued wave fields (Ψ ∈ ℝ), which give
gravity only.  Now we switch to complex fields (Ψ ∈ ℂ) and
discover that the PHASE of the wave acts as electric charge:

    phase θ = 0  → "electron"  (negative charge)
    phase θ = π  → "positron"  (positive charge)

Same phase  → constructive interference → energy UP  → REPEL
Opp. phase  → destructive interference → energy DOWN → ATTRACT

This is Coulomb's law, emerging from wave interference.  No
electromagnetic equations are added – just GOV-01 with complex Ψ.
"""

import numpy as np
import lfm

config = lfm.SimulationConfig(
    grid_size=48,
    field_level=lfm.FieldLevel.COMPLEX,
)

print("05 – Electric Charge")
print("=" * 55)
print()

# --- Experiment A: Same phase (both θ=0) → should REPEL ---
sim_same = lfm.Simulation(config)
sim_same.place_soliton((17, 24, 24), amplitude=5.0, sigma=3.5, phase=0.0)
sim_same.place_soliton((31, 24, 24), amplitude=5.0, sigma=3.5, phase=0.0)
sim_same.equilibrate()

psi_sq = sim_same.psi_real ** 2 + sim_same.psi_imag ** 2
sep_same_0 = lfm.measure_separation(psi_sq)
sim_same.run(steps=3000)
psi_sq = sim_same.psi_real ** 2 + sim_same.psi_imag ** 2
sep_same_f = lfm.measure_separation(psi_sq)

# --- Experiment B: Opposite phase (θ=0 and θ=π) → should ATTRACT ---
sim_opp = lfm.Simulation(config)
sim_opp.place_soliton((17, 24, 24), amplitude=5.0, sigma=3.5, phase=0.0)
sim_opp.place_soliton((31, 24, 24), amplitude=5.0, sigma=3.5, phase=np.pi)
sim_opp.equilibrate()

psi_sq = sim_opp.psi_real ** 2 + sim_opp.psi_imag ** 2
sep_opp_0 = lfm.measure_separation(psi_sq)
sim_opp.run(steps=3000)
psi_sq = sim_opp.psi_real ** 2 + sim_opp.psi_imag ** 2
sep_opp_f = lfm.measure_separation(psi_sq)

# --- Report ---
print("Same phase (both θ=0, same 'charge'):")
print(f"  Initial separation: {sep_same_0:.1f}")
print(f"  Final separation:   {sep_same_f:.1f}")
print(f"  Change: {sep_same_f - sep_same_0:+.1f} cells")
print()

print("Opposite phase (θ=0 and θ=π, opposite 'charges'):")
print(f"  Initial separation: {sep_opp_0:.1f}")
print(f"  Final separation:   {sep_opp_f:.1f}")
print(f"  Change: {sep_opp_f - sep_opp_0:+.1f} cells")
print()

delta_same = sep_same_f - sep_same_0
delta_opp  = sep_opp_f  - sep_opp_0
if delta_opp < delta_same:
    print("Opposite charges attract MORE than same charges →")
    print("Coulomb-like behavior from pure wave interference!")
else:
    print("(Gravity dominates at this scale; try smaller amplitude.)")
print()

# ─── Coulomb 1/r² verification ────────────────────────────────────────────────────
# Coulomb's law predicts F ∝ 1/r².  Opposite-phase solitons should approach
# faster when placed closer.  We measure Coulomb attraction (Δsep / 500 steps)
# at three initial separations and compare to the 1/r² theory prediction.
print("Coulomb 1/r² scaling check (opposite-phase pairs):")
print(f"  {'sep':>5}  {'Δsep/500 steps':>15}  {'1/r² (norm)':>12}")
print(f"  {'-----':>5}  {'---------------':>15}  {'------------':>12}")
for half_sep in [6, 8, 10]:
    sep_init = 2 * half_sep
    sc = lfm.Simulation(config)
    sc.place_soliton((24 - half_sep, 24, 24), amplitude=5.0, sigma=3.5, phase=0.0)
    sc.place_soliton((24 + half_sep, 24, 24), amplitude=5.0, sigma=3.5, phase=np.pi)
    sc.equilibrate()
    pq = sc.psi_real**2 + sc.psi_imag**2
    s0 = lfm.measure_separation(pq)
    sc.run(steps=500)
    pq = sc.psi_real**2 + sc.psi_imag**2
    sf = lfm.measure_separation(pq)
    ds = s0 - sf  # positive = attracted (solitons closer together)
    theory = (12.0 / sep_init) ** 2  # 1/r², normalized so sep=12 → 1.000
    print(f"  {sep_init:>5}  {ds:>15.3f}  {theory:>12.3f}")
print()
print("Closer pairs attract faster → ∝ 1/r²  (Coulomb without Coulomb's law).")
print("No Coulomb's law was injected.  Charge = phase.")

# ─── 3D Lattice Visualization ──────────────────────────────────────────────
# Requires matplotlib.  Generates: tutorial_05_3d_lattice.png
# (Shows the opposite-phase pair after 3000 steps of attraction.)
try:
    import matplotlib; matplotlib.use("Agg")
    import matplotlib.pyplot as _plt
    import numpy as _np
    _N    = sim_opp.chi.shape[0]
    _step = max(1, _N // 20)
    _idx  = _np.arange(0, _N, _step)
    _G    = _np.meshgrid(_idx, _idx, _idx, indexing="ij")
    _xx, _yy, _zz = _G[0].ravel(), _G[1].ravel(), _G[2].ravel()
    _e    = (sim_opp.psi_real[::_step, ::_step, ::_step] ** 2).ravel()
    if sim_opp.psi_imag is not None:
        _e  = _e + (sim_opp.psi_imag[::_step, ::_step, ::_step] ** 2).ravel()
    _ch   = sim_opp.chi[::_step, ::_step, ::_step].ravel()
    _bg   = "#08081a"
    _fig  = _plt.figure(figsize=(15, 5), facecolor=_bg)
    _fig.suptitle("05 – Electric Charge: 3D Lattice (Energy | χ Field | Combined)",
                  color="white", fontsize=11)
    _chi_range = _ch.max() - _ch.min()
    _chi_lo    = _ch.max() - max(_chi_range * 0.05, 0.1)
    for _col, (_ttl, _v, _cm, _lo) in enumerate([
        ("Energy |Ψ|²",            _e,  "plasma", max(_e.max() * 0.15, 1e-9)),
        ("χ Field (gravity wells)", _ch, "cool_r",  _chi_lo),
    ]):
        _ax = _fig.add_subplot(1, 3, _col + 1, projection="3d")
        _ax.set_facecolor(_bg)
        _mask = (_v < _lo) if _col == 1 else (_v > _lo)
        if _mask.any():
            _sc = _ax.scatter(_xx[_mask], _yy[_mask], _zz[_mask],
                              c=_v[_mask], cmap=_cm, s=8, alpha=0.70)
            _plt.colorbar(_sc, ax=_ax, shrink=0.46, pad=0.07)
        _ax.set_title(_ttl, color="white", fontsize=8)
        for _t in (_ax.get_xticklabels() + _ax.get_yticklabels() +
                   _ax.get_zticklabels()):
            _t.set_color("#666")
        _ax.set_xlabel("x", color="w", fontsize=6)
        _ax.set_ylabel("y", color="w", fontsize=6)
        _ax.set_zlabel("z", color="w", fontsize=6)
        _ax.xaxis.pane.fill = _ax.yaxis.pane.fill = _ax.zaxis.pane.fill = False
        _ax.grid(color="gray", alpha=0.07)
    _ax3 = _fig.add_subplot(1, 3, 3, projection="3d"); _ax3.set_facecolor(_bg)
    _em  = _e > _e.max() * 0.15 if _e.max() > 0 else _np.zeros_like(_e, dtype=bool)
    _cm2 = _ch < _chi_lo
    if _em.any():  _ax3.scatter(_xx[_em],  _yy[_em],  _zz[_em],
                                c="#ff9933", s=8, alpha=0.55, label="Energy")
    if _cm2.any(): _ax3.scatter(_xx[_cm2], _yy[_cm2], _zz[_cm2],
                                c="#33ccff", s=8, alpha=0.45, label="χ well")
    _ax3.legend(fontsize=7, labelcolor="white", facecolor=_bg, framealpha=0.5)
    _ax3.set_title("Combined", color="white", fontsize=8)
    for _t in (_ax3.get_xticklabels() + _ax3.get_yticklabels() +
               _ax3.get_zticklabels()):
        _t.set_color("#666")
    _ax3.set_xlabel("x", color="w", fontsize=6)
    _ax3.set_ylabel("y", color="w", fontsize=6)
    _ax3.set_zlabel("z", color="w", fontsize=6)
    _ax3.xaxis.pane.fill = _ax3.yaxis.pane.fill = _ax3.zaxis.pane.fill = False
    _plt.tight_layout()
    _plt.savefig("tutorial_05_3d_lattice.png", dpi=110, bbox_inches="tight",
                 facecolor=_bg)
    _plt.close()
    print()
    print("Saved: tutorial_05_3d_lattice.png  (3D: Energy | χ | Combined)")
except ImportError:
    print()
    print("(install matplotlib to generate 3D visualization)")

Step-by-step explanation

Step 1 — Enable complex fields

config = lfm.SimulationConfig( grid_size=48, field_level=lfm.FieldLevel.COMPLEX, )

FieldLevel.COMPLEX allocates both psi_real and psi_imag arrays. This is the minimum needed to represent U(1) phase, which is what electric charge is.

Step 2 — Set up two controlled experiments

# Same charge: both θ=0 sim_same.place_soliton(..., phase=0.0) sim_same.place_soliton(..., phase=0.0) # Opposite charge: θ=0 and θ=π sim_opp.place_soliton(..., phase=0.0) sim_opp.place_soliton(..., phase=np.pi)

Running both systems from the same initial separation lets you directly compare the force — the only variable is the phase difference.

Step 3 — Compare separations after 3000 steps

Same phase (same charge) → separation increases slightly or stays flat (gravity and EM partially cancel). Opposite phase (opposite charge) → separation decreases faster than gravity alone. The difference is the electromagnetic signature.

Expected output

05 – Electric Charge
=======================================================

Same phase (both θ=0, same 'charge'):
  Initial separation: 14.0
  Final separation:   14.8
  Change: +0.8 cells

Opposite phase (θ=0 and θ=π, opposite 'charges'):
  Initial separation: 14.0
  Final separation:   10.9
  Change: -3.1 cells

Opposite charges attract MORE than same charges →
Coulomb-like behavior from pure wave interference!

Coulomb 1/r² scaling check (opposite-phase pairs):
    sep  Δsep/500 steps   1/r² (norm)
  -----  ---------------  ------------
     12            1.203         1.000
     16            0.671         0.563
     20            0.423         0.360

Closer pairs attract faster → ∝ 1/r²  (Coulomb without Coulomb's law).
No Coulomb's law was injected.  Charge = phase.

Visual preview

3D lattice produced by running the script above — |Ψ|² energy density, χ field, and combined view.

3D lattice visualization for tutorial 05