HomeTutorials02 – Your First Particle
02Beginner~3 min

Your First Particle

Place a blob of energy on the lattice. GOV-02 responds by pullingχbelow 19 — creating a gravitational well. No Newton's law is added.

What you'll learn

  • How to place a soliton with sim.place_soliton(center, amplitude, sigma)
  • Why you must call sim.equilibrate() before evolving
  • How to interpret chi_min as gravitational well depth
  • That gravity emerges from GOV-02, not from Newton's law

Which equation is running?

LevelFieldForces includedSpeed
REAL ←Ψ ∈ ℝGravity onlyFastest (this tutorial)
COMPLEXΨ ∈ ℂGravity + EM~2× slower
FULLΨₐ ∈ ℂ³All 4 forces~6× slower

FULL is always physically correct — it is simply slower. This tutorial uses REAL because we are studying gravity only and real fields are 6× faster. Gravity in LFM comes entirely from the |ψ|² term in GOV-02, which behaves identically for real and complex fields.

The Key idea: GOV-02

GOV-02 is the equation that governs χ:

∂²χ/∂t² = c²∇²χ − κ(|Ψ|² − E₀²)

Where there is a concentration of wave energy |Ψ|², GOV-02 drives χ downward. Low χ means a stiffer medium with a deeper potential well — waves curve toward it. That curvature is gravity.

Full script

"""02 – Your First Particle

Drop a lump of energy onto the lattice and watch gravity appear.

In example 01 we saw that empty space has χ = 19 everywhere.
Now we add energy: a Gaussian soliton (a smooth blob of wave
amplitude).  GOV-02 says that energy density |Ψ|² pulls χ
downward.  Low χ = gravitational well.

This is gravity emerging from nothing but the wave equations.
"""

import lfm

config = lfm.SimulationConfig(grid_size=48)
sim = lfm.Simulation(config)

print("02 – Your First Particle")
print("=" * 55)
print()

# Place one soliton at the grid center.
center = (24, 24, 24)
sim.place_soliton(center, amplitude=5.0, sigma=4.0)

print("Before equilibration:")
m = sim.metrics()
print(f"  χ_min  = {m['chi_min']:.2f}  (should be 19 – no well yet)")
print()

# Equilibrate: solve Poisson equation so chi adjusts to the energy.
sim.equilibrate()

print("After equilibration (GOV-02 created the well):")
m = sim.metrics()
print(f"  χ_min  = {m['chi_min']:.2f}  (below 19 → gravity well!)")
print(f"  wells  = {m['well_fraction']*100:.1f}%  of the grid is a well")
print()

# Evolve – the soliton sits in its own gravity well.
sim.run(steps=2000)
m = sim.metrics()
print(f"After 2000 steps of evolution:")
print(f"  χ_min  = {m['chi_min']:.2f}")
print(f"  wells  = {m['well_fraction']*100:.1f}%")
print(f"  energy = {m['energy_total']:.2e}")
print()
print("The energy blob created a χ-well and sits inside it.")
print("No Newton's law was injected – gravity emerged from GOV-02.")
print()
print("Next: measure the shape of this well (→ 03).")

# ─── 3D Lattice Visualization ──────────────────────────────────────────────
# Requires matplotlib.  Generates: tutorial_02_3d_lattice.png
try:
    import matplotlib; matplotlib.use("Agg")
    import matplotlib.pyplot as _plt
    import numpy as _np
    _N    = sim.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.psi_real[::_step, ::_step, ::_step] ** 2).ravel()
    if sim.psi_imag is not None:
        _e  = _e + (sim.psi_imag[::_step, ::_step, ::_step] ** 2).ravel()
    _ch   = sim.chi[::_step, ::_step, ::_step].ravel()
    _bg   = "#08081a"
    _fig  = _plt.figure(figsize=(15, 5), facecolor=_bg)
    _fig.suptitle("02 – Your First Particle: 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_02_3d_lattice.png", dpi=110, bbox_inches="tight",
                 facecolor=_bg)
    _plt.close()
    print()
    print("Saved: tutorial_02_3d_lattice.png  (3D: Energy | χ | Combined)")
except ImportError:
    print()
    print("(install matplotlib to generate 3D visualization)")

Step-by-step explanation

Step 1 — Place the soliton

sim.place_soliton(center, amplitude=5.0, sigma=4.0)

This writes a Gaussian blob — amplitude × exp(−r²/2σ²) — into the Ψ field. At this point χ is still flat at 19 everywhere; the energy is just sitting there with no well yet.

Step 2 — Equilibrate

sim.equilibrate()

Solves the quasi-static Poisson equation (GOV-04) via FFT to find the χ profile that is consistent with the current energy distribution. After this, χ_min drops noticeably below 19.

Step 3 — Evolve and observe stability

sim.run(steps=2000) m = sim.metrics() print(m['chi_min'], m['wells'])

The soliton sits in its own χ-well and remains bound. The well depth stays roughly constant — the particle has found a stable, self-consistent configuration.

Expected output

02 – Your First Particle
=======================================================

Before equilibration:
  χ_min  = 19.00  (should be 19 – no well yet)

After equilibration (GOV-02 created the well):
  χ_min  = 16.43  (below 19 → gravity well!)
  wells  = 2.3%  of the grid is a well

After 2000 steps of evolution:
  χ_min  = 16.38
  wells  = 2.4%
  energy = 3.21e+03

The energy blob created a χ-well and sits inside it.
No Newton's law was injected – gravity emerged from GOV-02.

Next: measure the shape of this well (→ 03).

Visual preview

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

3D lattice visualization for tutorial 02