Self-Consistency Convergence
Overview
Self-consistency iterations are a critical component of the Liénard-Wiechert integrator that ensure numerical stability and physical accuracy in relativistic particle tracking. The integrator uses a dual independent convergence approach that simultaneously satisfies both:
Mass-shell constraint: \(P_t^2 - \mathbf{P}^2 = (mc)^2\)
Gamma consistency: \(\gamma_{\text{velocity}} = \gamma_{\text{energy}}\)
This dual-criterion approach prevents catastrophic numerical failures observed in high-energy close-approach scenarios where mass-shell alone is insufficient.
Warning
Without self-consistency iterations, the integrator can produce energy jumps exceeding 1000% in challenging scenarios (high-energy particles, close approaches, narrow apertures). Always enable self-consistency unless benchmarking or testing reference behavior.
Physical Motivation
The Circular Dependency Problem
In relativistic dynamics, there is a fundamental circular dependency:
Specifically:
Forces depend on retarded positions (which depend on velocities)
Velocities depend on \(\gamma = (P_t - q\Phi)/(mc)\) (which depends on forces)
The scalar potential \(\Phi = \sum_j q_j / R_j\) depends on separation distances
This creates a chicken-and-egg problem: we need \(\gamma\) to compute forces, but we need forces to compute \(\gamma\).
The Self-Consistency Solution
Self-consistency breaks this circular dependency through iteration:
Start with a trial \(\gamma\) from the previous timestep
Compute forces using this trial \(\gamma\)
Update momentum \(\mathbf{P}' = \mathbf{P} + h \mathbf{F}\)
Compute new \(\gamma'\) from updated momentum
Check convergence: Are the constraints satisfied?
If NO, use \(\gamma'\) as the new trial and repeat from step 2
If YES, accept the step
Dual Independent Convergence Criteria
Why Two Criteria?
Testing revealed that mass-shell convergence alone is insufficient in extreme regimes:
Scenario |
Mass-Shell Error |
Gamma Error |
|---|---|---|
Low energy (γ=2, sep=10mm) |
2.5×10⁻¹⁴ ✓ |
1.0×10⁻¹⁴ ✓ |
Moderate (γ=50, sep=5mm) |
2.3×10⁻¹¹ ✓ |
4.6×10⁻¹² ✓ |
High (γ=50, sep=0.5mm) |
5.2×10⁻⁷ ✓ |
2.1×10⁶ ✗ |
High (γ=200, sep=1mm) |
1.8×10⁻⁸ ✓ |
4.8×10⁵ ✗ |
In high-energy close-approach scenarios, the mass-shell can converge while gamma diverges by orders of magnitude (errors up to 10⁶). This indicates complete numerical breakdown despite satisfying the mass-shell constraint.
What Each Criterion Validates
- Mass-Shell Constraint (\(E_{\text{ms}}\))
Validates the energy-momentum relation:
\[E_{\text{ms}} = \frac{|P_t^2 - \mathbf{P}^2 - (mc)^2|}{(mc)^2}\]When this is satisfied, the particle’s four-momentum lies on the relativistic mass shell. This is a fundamental requirement from special relativity.
- Gamma Consistency (\(E_{\gamma}\))
Validates position-velocity-potential consistency:
\[E_{\gamma} = \frac{|\gamma_{\text{velocity}} - \gamma_{\text{energy}}|}{\gamma}\]Where:
\[\begin{split}\gamma_{\text{velocity}} &= \frac{1}{\sqrt{1 - \beta^2}} \quad \text{(from kinematics)} \\ \gamma_{\text{energy}} &= \frac{P_t - q\Phi}{mc} \quad \text{(from four-momentum)}\end{split}\]When \(\gamma_{\text{velocity}} \neq \gamma_{\text{energy}}\), the position update is corrupted due to numerical instability in the scalar potential term \(\Phi = \sum_j q_j / R_j\), which diverges as \(R \rightarrow 0\).
Note
Both criteria are necessary. Mass-shell ensures energy-momentum consistency, while gamma ensures position-velocity-potential consistency. Together, they provide robust validation of the complete particle state.
Working Beta and Working Gamma
The Iteration Variables
During self-consistency iterations, the integrator maintains working variables that evolve across iterations:
# Initialize from current timestep state
working_beta_x = current_state["bx"][i]
working_beta_y = current_state["by"][i]
working_beta_z = current_state["bz"][i]
working_gamma = current_state["gamma"][i]
# Self-consistency loop
for iteration in range(max_iterations):
# Use working variables to compute forces
forces = compute_forces(working_beta, working_gamma, ...)
# Update momentum
P_new = P_old + h * forces
# Compute new gamma from energy
gamma_new = (Pt_new - q*Phi) / (m*c)
# Compute new beta from momentum and gamma
beta_new = P_new / (gamma_new * m * c)
# Check convergence (both criteria)
if converged(beta_new, gamma_new):
break
# Update working variables for next iteration
working_beta = beta_new
working_gamma = gamma_new
How Working Variables Are Calculated
- Iteration 0 (Initial Guess)
The working variables are initialized from the previous timestep:
\[\begin{split}\beta_{\text{work}}^{(0)} &= \beta^{n-1} \quad \text{(from current state)} \\ \gamma_{\text{work}}^{(0)} &= \gamma^{n-1} \quad \text{(from current state)}\end{split}\]This is our “guess” for what \(\beta\) and \(\gamma\) will be at the new timestep \(n\).
- Iteration k ≥ 1 (Refinement)
At each iteration \(k\), we:
Compute retarded distances using \(\beta_{\text{work}}^{(k-1)}\):
\[R_j^{\text{ret}} = |\mathbf{r}_i(t) - \mathbf{r}_j(t_{\text{ret}})|\]where the retarded time satisfies:
\[t_{\text{ret}} = t - \frac{R_j^{\text{ret}}}{c}\]Compute electromagnetic forces at retarded positions:
\[\mathbf{F} = q(\mathbf{E}_{\text{ret}} + \beta_{\text{work}}^{(k-1)} \times \mathbf{B}_{\text{ret}})\]Update conjugate four-momentum:
\[\begin{split}\mathbf{P}^{(k)} &= \mathbf{P}^{n-1} + h \mathbf{F} \\ P_t^{(k)} &= P_t^{n-1} + h (\mathbf{F} \cdot \beta_{\text{work}}^{(k-1)})\end{split}\]Compute scalar potential from external charges:
\[\Phi^{(k)} = \sum_j \frac{q_j}{R_j^{\text{ret}}}\]Compute gamma from energy (four-momentum):
\[\gamma_{\text{energy}}^{(k)} = \frac{P_t^{(k)} - q\Phi^{(k)}}{mc}\]Compute beta from momentum:
\[\beta^{(k)} = \frac{\mathbf{P}^{(k)}}{\gamma_{\text{energy}}^{(k)} \cdot mc}\]Update position using new \(\beta\) and \(\gamma\):
\[\mathbf{x}^{(k)} = \mathbf{x}^{n-1} + h \beta^{(k)} \gamma_{\text{energy}}^{(k)}\]Compute gamma from velocity (kinematics):
\[\begin{split}\beta^2 &= (\beta_x^{(k)})^2 + (\beta_y^{(k)})^2 + (\beta_z^{(k)})^2 \\ \gamma_{\text{velocity}}^{(k)} &= \frac{1}{\sqrt{1 - \beta^2}}\end{split}\]Check both convergence criteria (see next section)
If not converged, update working variables for next iteration:
\[\begin{split}\beta_{\text{work}}^{(k)} &= \beta^{(k)} \\ \gamma_{\text{work}}^{(k)} &= \gamma_{\text{energy}}^{(k)}\end{split}\]
Important
The working variables evolve self-consistently across iterations. Each iteration uses the previous iteration’s \((\beta, \gamma)\) to compute forces, which in turn produce a refined \((\beta', \gamma')\). The loop continues until both the mass-shell and gamma consistency criteria are satisfied.
Convergence Algorithm
Dual Independent Mode
In dual_independent mode (default), both criteria must be satisfied:
# Compute errors
E_ms = |Pt² - P² - (mc)²| / (mc)²
E_gamma = |γ_velocity - γ_energy| / γ
# Check both criteria
mass_shell_converged = (E_ms < target_ms_tolerance)
gamma_converged = (E_gamma < target_gamma_tolerance)
# Both must be true
converged = mass_shell_converged AND gamma_converged
Flowchart
┌──────────────────────────────────────────────────────────────────────┐
│ ITERATION k │
└──────────────────────────────────────────────────────────────────────┘
│
├─ [1] Use working_beta^(k-1), working_gamma^(k-1)
│
├─ [2] Compute retarded distances to external particles
│ R_j^ret = |r_i(t) - r_j(t_ret)|
│
├─ [3] Compute electromagnetic forces at retarded positions
│ F = q(E_ret + beta_work × B_ret)
│
├─ [4] Update conjugate four-momentum
│ P^(k) = P^(n-1) + h * F
│ Pt^(k) = Pt^(n-1) + h * (F · beta_work)
│
├─ [5] Compute scalar potential
│ Φ^(k) = Σ(q_j / R_j^ret)
│
├─ [6] Compute gamma from energy (four-momentum)
│ γ_energy^(k) = (Pt^(k) - q*Φ^(k)) / (mc)
│
├─ [7] Compute beta from momentum
│ β^(k) = P^(k) / (γ_energy^(k) * mc)
│
├─ [8] Update position
│ x^(k) = x^(n-1) + h * β^(k) * γ_energy^(k)
│
├─ [9] Compute gamma from velocity (kinematics)
│ γ_velocity^(k) = 1 / √(1 - β²)
│
└─ [10] Check both convergence criteria (if k > 0)
│
├─ Compute mass-shell error:
│ E_ms = |Pt² - P² - (mc)²| / (mc)²
│
├─ Compute gamma consistency error:
│ E_gamma = |γ_velocity - γ_energy| / γ
│
└─ Convergence criterion:
│
├─ mass_shell_converged = (E_ms < target_ms_tol)
├─ gamma_converged = (E_gamma < target_gamma_tol)
└─ converged = (BOTH conditions true)
│
├─ If YES: ✓ CONVERGED → Accept step, exit loop
│
└─ If NO:
│
├─ If k == max_iter - 1:
│ ⚠️ Max iterations reached
│ → Apply safety projection
│ → Log warning
│ → Exit loop
│
└─ Else:
Update working variables:
working_beta^(k) = β^(k)
working_gamma^(k) = γ_energy^(k)
→ Continue to iteration k+1
Example: Low Energy Case
For a low-energy particle (γ=2, separation=10mm):
Iteration 0: (initial guess from previous timestep)
working_beta = [bx, by, bz] from t^(n-1)
working_gamma = 2.0 from t^(n-1)
Iteration 1:
→ Compute forces using working_beta, working_gamma
→ Update momentum: P_new, Pt_new
→ Compute γ_energy = 2.00000000000001
→ Compute β_new from P_new
→ Compute γ_velocity = 2.00000000000002
→ Check errors:
E_ms = 3.2e-14 ✓ (< 1e-6)
E_gamma = 1.1e-15 ✓ (< 1e-6)
→ CONVERGED in 1 iteration
Result: Excellent convergence at machine precision
Example: High Energy Close Approach
For a challenging case (γ=200, separation=1mm) with both convergence criteria:
Iteration 0: (initial guess)
working_beta = [bx, by, bz] from t^(n-1)
working_gamma = 200.0 from t^(n-1)
Iteration 1:
→ Compute forces using working_beta, working_gamma
→ Update momentum
→ γ_energy = 200.234, γ_velocity = 206.415
→ E_ms = 5.2e-7 ✓, E_gamma = 3.1e-2 ✗
→ NOT CONVERGED (gamma error too large)
→ Update: working_gamma = 200.234
Iteration 2:
→ Recompute forces with updated working_gamma
→ γ_energy = 200.187, γ_velocity = 200.672
→ E_ms = 1.8e-8 ✓, E_gamma = 2.4e-3 ✗
→ NOT CONVERGED (gamma still too large)
Iteration 3:
→ γ_energy = 200.143, γ_velocity = 200.287
→ E_ms = 6.3e-10 ✓, E_gamma = 1.5e-4 ✗
→ NOT CONVERGED
Iteration 4:
→ γ_energy = 200.128, γ_velocity = 200.146
→ E_ms = 2.1e-11 ✓, E_gamma = 8.7e-6 ✗
→ NOT CONVERGED
Iteration 5:
→ γ_energy = 200.124, γ_velocity = 200.126
→ E_ms = 7.5e-13 ✓, E_gamma = 4.2e-7 ✓
→ CONVERGED in 5 iterations
Without the gamma-consistency check:
Iteration 1:
→ E_ms = 5.2e-7 ✓ (< 1e-6)
→ CONVERGED on mass-shell error alone
→ Gamma check: E_gamma = 3.1e-2 ⚠️ WARNING
Subsequent steps:
→ Gamma error grows to 10⁶ (catastrophic failure)
This demonstrates why checking both convergence criteria is essential for extreme regimes.
Safety Nets
Post-Loop Mass-Shell Projection
After the iteration loop completes (either converged or max iterations reached), a final safety check is performed:
# Final mass-shell check
E_ms_final = |Pt² - P² - (mc)²| / (mc)²
if E_ms_final > mass_shell_tolerance: # e.g., 1e-2 (1%)
# Apply projection as fallback
Pt_corrected = √(P² + (mc)²)
# Recalculate gamma with projected Pt
gamma = (Pt_corrected - q*Φ) / (mc)
# Log warning
print(f"⚠️ Applied mass-shell projection (error was {E_ms_final:.2e})")
This acts as a safety net if the iteration loop fails to converge within max_iterations. The projection ensures the particle state satisfies the mass-shell constraint, preventing catastrophic energy violations.
Note
The safety net threshold (mass_shell_tolerance, default 1e-2) is much
looser than the target tolerance (target_ms_tolerance, default 1e-6).
The goal is to converge to high precision; the safety net only activates
in extreme failure cases.
Configuration
Standard Configuration
For typical high-energy particle tracking:
from core.self_consistency import SelfConsistencyConfig
config = SelfConsistencyConfig(
enabled=True,
convergence_mode="dual_independent",
target_ms_tolerance=1e-6,
target_gamma_tolerance=1e-6,
mass_shell_tolerance=1e-2,
max_iterations=10,
verbosity=0
)
Aggressive Configuration
For ultra-relativistic particles (γ > 1000) or narrow apertures:
config = SelfConsistencyConfig(
enabled=True,
convergence_mode="dual_independent",
target_ms_tolerance=1e-8, # 100x stricter
target_gamma_tolerance=1e-8,
mass_shell_tolerance=1e-3,
max_iterations=15, # More iterations
verbosity=2 # Detailed logging
)
Benchmarking Configuration
For comparison runs or lower-cost benchmarking:
config = SelfConsistencyConfig(
enabled=True,
convergence_mode="fixed_geometry", # Fixed geometry, Pt projection
target_ms_tolerance=1e-6,
max_iterations=5,
verbosity=0
)
Warning
Fixed-geometry self-consistency is the fast maintained path, but challenging close-approach scenarios may still require tighter tolerances or more iterations for stable convergence.
Disabling Self-Consistency
For debugging or reference comparisons:
config = SelfConsistencyConfig(enabled=False)
Danger
Disabling self-consistency can produce energy jumps exceeding 1000% and non-physical particle trajectories. Only disable for testing or controlled reference comparisons. Never use in production simulations.
Performance Considerations
Typical Iteration Counts
Scenario |
Avg Iterations |
|---|---|
Low energy (γ < 10) |
1-2 |
Moderate (10 < γ < 100) |
2-4 |
High energy (100 < γ) |
3-6 |
Ultra-relativistic (γ>1000) |
5-10 |
Close approach (< 1mm) |
5-15 |
Computational Cost
Each self-consistency iteration requires:
Retarded distance calculation (~10 FLOPS per external particle)
Electromagnetic force evaluation (~50 FLOPS per external particle)
Momentum update (~20 FLOPS)
Gamma/beta recalculation (~30 FLOPS)
Convergence checks (~10 FLOPS)
For typical cases (2-4 iterations), the overhead is negligible compared to the retarded field calculations. The benefit (preventing energy jumps and ensuring physical accuracy) far outweighs the cost.
When to Increase max_iterations
Increase max_iterations if you observe:
Frequent “max iterations reached” warnings
Non-convergence in high-energy scenarios
Energy jumps despite self-consistency enabled
Recommended values:
Standard: 10 (sufficient for γ < 1000)
Aggressive: 15 (for γ > 1000 or close approaches)
Ultra-aggressive: 20 (for γ > 10000 or extreme regimes)
Verbosity Options
Silent Mode (verbosity=0)
No convergence output. Use for production runs.
Basic Mode (verbosity=1)
One line per particle showing convergence status:
P0: converged in 3 iter, E_ms=2.1e-11, E_gamma=3.5e-9
Detailed Mode (verbosity=2)
Full convergence details with 15-digit precision:
Particle 0: converged in 3 iter
Mass-shell error = 2.145678901234567e-11
Gamma consistency error = 3.456789012345678e-09
Dual convergence: BOTH criteria must be satisfied
γ_velocity (from β) = 1.234567890123456e+02
γ_energy (from Pt - q·Φ) = 1.234567890123459e+02
γ_mass_shell (√(P²+(mc)²)/(mc)) = 1.234567890123457e+02
Use for debugging convergence issues or investigating numerical precision.
Gamma Reconciliation
The Dual-Gamma Problem
The integrator computes the Lorentz factor γ in two independent ways:
γ_energy (from conjugate momentum):
\[\gamma_{\text{energy}} = \frac{P_t - q\Phi}{mc}\]γ_velocity (from velocity):
\[\gamma_{\text{velocity}} = \frac{1}{\sqrt{1 - \beta^2}}\]
In exact mathematics, these should be identical. However, numerical discretization can create small differences that accumulate over long integration runs, potentially causing:
Energy jumps and conservation violations
Catastrophic gamma blowups (γ → ∞)
Numerical instabilities in high-energy regimes
Reconciliation Methods
To address this dual-gamma inconsistency, five reconciliation methods are available
via core.types.GammaReconciliationMethod:
- ADAPTIVE_WEIGHTED
Velocity-dependent weighted average with configurable thresholds:
\[\gamma_{\text{reconciled}} = \alpha(\beta) \cdot \gamma_{\text{energy}} + (1-\alpha(\beta)) \cdot \gamma_{\text{velocity}}\]Where α varies by velocity regime:
β < 0.9: α = 0.8 (trust energy more)
β > 0.99: α = 0.2 (trust velocity more)
0.9 ≤ β ≤ 0.99: α = 0.5 (balanced)
After reconciliation, both γ and Pt are updated, and spatial momentum is rescaled to preserve the mass shell: Pt² = P² + (mc)²
- FIXED_WEIGHTED
Fixed 50/50 blend (or custom weight) across all velocities:
\[\gamma_{\text{reconciled}} = \alpha \cdot \gamma_{\text{energy}} + (1-\alpha) \cdot \gamma_{\text{velocity}}\]Default α = 0.5, configurable via
gamma_reconciliation_fixed_weight- USE_VELOCITY
Always use γ_velocity = 1/√(1-β²)
Warning
This breaks energy bookkeeping and can cause unphysical energy changes. Only use for testing/debugging.
- USE_ENERGY
Always use γ_energy = (Pt - q·Φ)/(mc)
Equivalent to DISABLED; provided for symmetry/clarity.
- DISABLED (default as of February 2026)
No reconciliation applied
Note
This is now the recommended default as of February 2026. The original reconciliation implementation violated energy conservation by overwriting Pt without preserving the scalar potential contribution (q·Φ), causing -99% energy losses in some parameter regimes.
The feature remains available for opt-in use but requires redesign before safe re-enablement. See the February 2026 updates in recent_changes.rst for details.
Configuration
Configure via SelfConsistencyConfig:
from core.self_consistency import SelfConsistencyConfig
from core.types import GammaReconciliationMethod
# Example 1: Default (ADAPTIVE_WEIGHTED with standard thresholds)
config = SelfConsistencyConfig()
# Uses ADAPTIVE_WEIGHTED with β thresholds [0.9, 0.99] and weights [0.8, 0.5, 0.2]
# Example 2: Custom adaptive weighting for ultra-relativistic particles
config = SelfConsistencyConfig(
gamma_reconciliation_method=GammaReconciliationMethod.DISABLED, # Default as of Feb 2026
gamma_reconciliation_low_beta_threshold=0.85,
gamma_reconciliation_high_beta_threshold=0.995,
gamma_reconciliation_low_beta_weight=0.9, # Trust energy more at low β
gamma_reconciliation_high_beta_weight=0.1, # Trust velocity more at high β
gamma_reconciliation_mid_beta_weight=0.5
)
# Example 3: Fixed weighted blend
config = SelfConsistencyConfig(
gamma_reconciliation_method=GammaReconciliationMethod.FIXED_WEIGHTED,
gamma_reconciliation_fixed_weight=0.6 # 60% energy, 40% velocity
)
# Example 4: Disable reconciliation (not recommended)
config = SelfConsistencyConfig(
gamma_reconciliation_method=GammaReconciliationMethod.DISABLED
)
API Usage
Configure via lw_integrator.testbed_runner.SimulationOptions:
from lw_integrator.testbed_runner import SimulationOptions
from core.types import SimulationType
# Example 2: Opt-in to adaptive weights (not recommended as of Feb 2026)
options = SimulationOptions(
simulation_type=SimulationType.CONDUCTING_WALL,
steps=1000,
self_consistency_gamma_reconciliation_method='ADAPTIVE_WEIGHTED', # Opt-in only
self_consistency_gamma_reconciliation_low_beta_threshold=0.9,
self_consistency_gamma_reconciliation_high_beta_threshold=0.99,
self_consistency_gamma_reconciliation_low_beta_weight=0.8,
self_consistency_gamma_reconciliation_high_beta_weight=0.2,
self_consistency_gamma_reconciliation_mid_beta_weight=0.5
)
GUI Controls
In the GUI, navigate to Stability tab → Self-Consistency Checks → Gamma Reconciliation:
Method dropdown: Select from 5 reconciliation methods
Adaptive Weighted Parameters panel: Configure thresholds and weights (shown for ADAPTIVE_WEIGHTED)
Fixed Weighted Parameter panel: Configure fixed weight (shown for FIXED_WEIGHTED)
Panels automatically show/hide based on the selected method.
Backward Compatibility
The old boolean gamma_reconciliation_enabled parameter has been replaced with
gamma_reconciliation_method. Historical configs should be updated to use the
enum directly:
# Maintained style
config.gamma_reconciliation_method = GammaReconciliationMethod.ADAPTIVE_WEIGHTED
config.gamma_reconciliation_method = GammaReconciliationMethod.DISABLED
Recommendations
- Production use: DISABLED (default as of February 2026)
Matches v0.4.8 stable behavior
Ensures energy conservation
Recommended until reconciliation feature is redesigned
No reconciliation applied (uses γ_energy directly)
- Custom tuning: For ultra-relativistic particles (β > 0.999)
Lower
high_beta_thresholdto 0.995Lower
high_beta_weightto 0.1 (trust velocity more)
Debugging: Use USE_VELOCITY or USE_ENERGY to isolate problematic calculations
Diagnostics
With verbosity >= 3, reconciliation details are logged:
Gamma reconciliation (ADAPTIVE_WEIGHTED): α=0.80, β=0.856000,
γ_energy=2.123456e+00, γ_velocity=2.123401e+00, γ_reconciled=2.123445e+00
Interpretation:
α: Weight applied to γ_energy (1-α applied to γ_velocity)
β: Total velocity magnitude
γ_energy: Gamma from conjugate momentum
γ_velocity: Gamma from velocity
γ_reconciled: Final blended value
Watch for large discrepancies (>1% relative difference) between γ_energy and γ_velocity, which indicate numerical instability requiring reconciliation.
References
Conjugate momentum and equations of motion - Full equations of motion documentation
Adaptive Timestep System - Integration loop and timestep management
See Also
core.self_consistency.SelfConsistencyConfig- Configuration classcore.types.GammaReconciliationMethod- Reconciliation method enumcore.equations.retarded_equations_of_motion()- Main equations implementationcore.self_consistency.self_consistent_step()- Wrapper function