Adaptive Timestep System
Overview
The adaptive timestep system automatically refines integration timesteps when energy jumps or gamma blowups are detected, preventing numerical instabilities while maintaining computational efficiency.
As of February 2026, the system uses auto-calculated parameters to ensure mathematical consistency and prevent configuration errors.
Key Features
Energy jump detection - Monitors relative energy changes between steps
Gamma blowup protection - Catches unphysical gamma values (γ > 10⁷ or γ < 1)
Automatic refinement - Reduces timestep by configurable factor when issues detected
Hysteresis logic - Stays at reduced timestep for stability before attempting recovery
Proximity-based refinement - Reduces timestep near walls/apertures proactively
Auto-calculated parameters - Derived values computed automatically for consistency
How It Works
Detection and Refinement
At each integration step, the adaptive timestep system:
Executes step with current timestep
hChecks energy change:
|ΔE/E| < threshold(default: 10%)Checks gamma validity:
1 ≤ γ ≤ 10⁷If violation detected:
Reduce timestep:
h_new = h / reduction_factorRetry the step with reduced timestep
Repeat until step succeeds or max attempts reached
If max attempts exceeded → mark particle as dead
Hysteresis and Recovery
Once timestep is reduced, the system uses hysteresis to prevent oscillation:
- Cooldown Phase (default: 10 steps)
Stay at reduced timestep to ensure stability
- Probing Phase (default: 3 steps)
Test whether base timestep can be safely restored:
Monitor energy changes
If stable (
|ΔE/E| < 1%) for 3 consecutive steps → return to base timestepIf unstable → return to cooldown
This prevents rapid cycling between base and reduced timesteps.
Proximity-Based Refinement
When particles approach walls or apertures, timestep is reduced proactively to improve accuracy in high-field regions:
Full reduction zone: distance < 0.5 aperture radii
Transition zone: 0.5-1.0 aperture radii (linear ramp)
Normal zone: distance > 1.0 aperture radii
Configuration
Auto-Calculated Parameters (February 2026)
As of February 2026, you only configure 2 independent parameters:
from core.integration_runner import AdaptiveTimestepConfig
config = AdaptiveTimestepConfig(
enabled=True,
# === USER-CONFIGURABLE PARAMETERS ===
timestep_reduction_factor=3, # How aggressively to reduce (2, 3, or 10 typical)
min_timestep_factor=1e-4, # Minimum timestep as fraction of base
# === AUTO-CALCULATED (read-only properties) ===
# max_refinement_attempts: computed from above two parameters
# max_substeps_per_step: computed from min_timestep_factor
)
The derived parameters are calculated as:
Why auto-calculation?
Ensures minimum timestep is always reachable within max attempts
Prevents time discontinuities from undersized substep caps
Eliminates overdetermined configurations
Reduces user confusion
Example Calculations
reduction_factor |
min_factor |
max_attempts |
max_substeps |
|---|---|---|---|
3 |
1e-4 |
9 |
11,000 |
10 |
1e-4 |
4 |
11,000 |
3 |
1e-3 |
6 |
1,100 |
10 |
1e-3 |
3 |
1,100 |
Full Configuration Options
from core.integration_runner import AdaptiveTimestepConfig
config = AdaptiveTimestepConfig(
# === Enable/disable ===
enabled=True,
# === Detection thresholds ===
energy_jump_threshold=0.10, # Relative energy change (10%)
gamma_max_threshold=1e7, # Maximum allowed gamma
gamma_min_threshold=1.0, # Minimum allowed gamma
# === Refinement parameters ===
timestep_reduction_factor=3, # Reduce timestep by this factor
min_timestep_factor=1e-4, # Min timestep = base * min_factor
# max_refinement_attempts: AUTO-CALCULATED
# === Hysteresis parameters ===
cooldown_steps=10, # Steps at reduced timestep before probing
probe_threshold=0.01, # Energy stability for recovery (1%)
max_probe_steps=3, # Consecutive stable steps needed
# === Proximity refinement ===
proximity_refinement_enabled=True,
proximity_distance_aperture_radii=0.5, # Start full reduction
proximity_transition_aperture_radii=1.0, # End reduction ramp
proximity_reduction_factor=10.0, # Timestep reduction in proximity
# === Particle death handling ===
skip_cooldown_on_particle_death=False, # Keep survivors in cooldown
# === Debugging ===
debug=False, # Enable verbose logging
)
Usage Examples
Basic Configuration
from lw_integrator.testbed_runner import SimulationOptions
options = SimulationOptions(
steps=1000,
time_step=1e-6, # Base timestep in ns
# Enable adaptive timestep with defaults
adaptive_timestep_enabled=True,
adaptive_timestep_threshold=0.10, # 10% energy change threshold
adaptive_timestep_reduction_factor=3, # Reduce by 3× when needed
adaptive_timestep_min_factor=1e-4, # Min = base/10000
# max_attempts auto-calculated: 9 attempts
)
Conservative Configuration (High Stability)
For challenging simulations (narrow apertures, high energies):
options = SimulationOptions(
steps=1000,
time_step=1e-6,
adaptive_timestep_enabled=True,
adaptive_timestep_threshold=0.05, # More sensitive (5%)
adaptive_timestep_reduction_factor=10, # Aggressive reduction
adaptive_timestep_min_factor=1e-5, # Very small minimum
adaptive_timestep_cooldown_steps=20, # Longer cooldown
# max_attempts auto-calculated: 5 attempts
)
Aggressive Configuration (Speed Priority)
For well-behaved simulations where speed matters:
options = SimulationOptions(
steps=1000,
time_step=1e-6,
adaptive_timestep_enabled=True,
adaptive_timestep_threshold=0.20, # Less sensitive (20%)
adaptive_timestep_reduction_factor=2, # Gentle reduction
adaptive_timestep_min_factor=1e-3, # Larger minimum
adaptive_timestep_cooldown_steps=5, # Shorter cooldown
# max_attempts auto-calculated: 10 attempts
)
Direct API Usage
from core.integration_runner import retarded_integrator, AdaptiveTimestepConfig
from core.self_consistency import SelfConsistencyConfig
# Create configs
adaptive_config = AdaptiveTimestepConfig(
enabled=True,
timestep_reduction_factor=3,
min_timestep_factor=1e-4,
debug=True # See timestep refinements in console
)
sc_config = SelfConsistencyConfig(enabled=True)
# Run integrator
trajectory, trajectory_ext = retarded_integrator(
init_rider=init_rider,
init_driver=init_driver,
h_step=1e-6,
steps=1000,
aperture_radius=0.05,
sim_type=SimulationType.CONDUCTING_WALL,
self_consistency=sc_config,
adaptive_timestep=adaptive_config,
)
GUI Configuration
In the GUI, navigate to Stability tab → Adaptive Timestep:
Enable checkbox - Turn adaptive timestep on/off
Energy Jump Threshold - Relative energy change trigger (default: 10%)
Reduction Factor - How aggressively to reduce timestep (default: 3)
Min Timestep Factor - Minimum allowed timestep fraction (default: 1e-4)
Max Refinement Attempts - Read-only, auto-calculated display
Cooldown Steps - Steps at reduced timestep before recovery (default: 10)
Debug Logging - Enable verbose diagnostics (console only for single runs)
The Max Refinement Attempts field shows the calculated value in italic gray text with an explanation tooltip.
Performance Considerations
Computational Cost
Adaptive timestep adds overhead:
Detection checks: ~1-2% per step (negligible)
Refinement retries: Depends on problem difficulty
Sub-stepping: Linear cost in number of substeps
Typical overhead:
Well-behaved simulations: < 5%
Challenging regions: 10-50% (but prevents failures)
Pathological cases: Can increase runtime 2-10× (but enables completion)
When to Enable
Always enable for:
High-energy particles (γ > 100)
Small apertures (< 0.1 mm)
Close approaches to conducting walls
Long integration runs (> 1000 steps)
Production simulations
Consider disabling for:
Benchmarking (compare with/without)
Legacy validation runs
Very well-behaved test cases
Quick parameter scans where failures are acceptable
Tuning Guidelines
Reduction Factor
Controls how aggressively timestep is reduced:
- reduction_factor = 2
Gentle reduction, many attempts needed
Good for smooth problems
Slower convergence in pathological cases
- reduction_factor = 3 (default)
Balanced approach
Works well for most cases
Recommended starting point
- reduction_factor = 10
Aggressive reduction, few attempts needed
Good for highly unstable problems
Can overshoot optimal timestep
Minimum Timestep Factor
Controls how small timestep can become:
- min_factor = 1e-3 (relaxed)
Minimum = 0.001 × base timestep
Fast but may not resolve extreme cases
Use for well-behaved simulations
- min_factor = 1e-4 (default)
Minimum = 0.0001 × base timestep
Good balance for most problems
Recommended default
- min_factor = 1e-5 (tight)
Minimum = 0.00001 × base timestep
Very conservative, handles extreme cases
Can be slow in challenging regions
Energy Jump Threshold
Controls sensitivity to energy changes:
- threshold = 0.05 (5%)
Very sensitive, triggers often
Good for strict energy conservation
May be overly conservative
- threshold = 0.10 (10%, default)
Balanced sensitivity
Works well for most physics cases
Recommended default
- threshold = 0.20 (20%)
Less sensitive, fewer refinements
Faster but less conservative
Only for well-understood problems
Debugging
Debug Logging
Enable verbose diagnostics:
config = AdaptiveTimestepConfig(
enabled=True,
debug=True, # Prints to console
# ... other settings
)
Sample output:
Step 42: Energy jump detected (15.3% change). Reducing timestep: 1.000e-06 → 3.333e-07 ns
Step 42: Retry attempt 1/9 with h=3.333e-07 ns
Step 43: Cooldown mode (1/10), using reduced timestep 3.333e-07 ns
Step 52: Cooldown complete - probing stability with reduced timestep (0/3 stable)
Step 55: Probing successful. Returning to base timestep 1.000e-06 ns
Batched Logging (February 2026)
For GUI applications, use batched logging to prevent unresponsiveness:
from core.batched_logger import BatchedLogger
# Create batched logger
logger = BatchedLogger(
target_logger=my_gui_logger.log,
batch_size=50 # Flush every 50 messages
)
# Pass to integrator
trajectory, trajectory_ext = retarded_integrator(
...,
adaptive_timestep=AdaptiveTimestepConfig(debug=True),
logger=logger.log # Use batched logger
)
This reduces GUI updates by ~100× while preserving all messages.
Common Issues
Issue: Time discontinuities (particle “jumps” in trajectory)
Cause: max_substeps_per_step too small (now auto-calculated, shouldn’t occur)
Solution: Update to February 2026 version with auto-calculation
Issue: Integration never completes (stuck in refinement loop)
Cause: Problem too stiff for adaptive timestep to handle
Solutions:
Increase
min_timestep_factor(e.g., 1e-5)Increase
timestep_reduction_factor(e.g., 10)Check physics: may indicate particle hitting wall (should be marked dead)
Issue: Too many refinements, simulation very slow
Cause: Base timestep too large for problem
Solution: Reduce base timestep or relax energy_jump_threshold
Issue: Particle marked dead unexpectedly
Cause: Max refinement attempts exhausted
Solutions:
Reduce
min_timestep_factor(allow smaller timesteps)Check if particle actually hit wall (correct behavior)
Enable debug logging to see refinement history
Migration from Old Version
If you have code from before February 2026:
Old style (no longer works):
config = AdaptiveTimestepConfig(
enabled=True,
max_refinement_attempts=5, # ERROR: no longer accepted
max_substeps_per_step=1000, # ERROR: no longer accepted
)
New style (auto-calculated):
config = AdaptiveTimestepConfig(
enabled=True,
timestep_reduction_factor=3, # Set this
min_timestep_factor=1e-4, # And this
# max_refinement_attempts auto-calculated: 9
# max_substeps_per_step auto-calculated: 11000
)
To achieve similar behavior to old max_attempts=5:
# Old: max_attempts=5 with reduction_factor=10, min_factor=1e-4
# gave: 5 attempts, min = base/10000
# New equivalent:
config = AdaptiveTimestepConfig(
timestep_reduction_factor=10, # Same reduction
min_timestep_factor=1e-4, # Same minimum
# Auto-calculates: max_attempts=4 (close to old value of 5)
)
See Also
Self-Consistency Convergence - Self-consistency convergence system
Recent Changes - Recent changes and updates
core/integration_runner.py- Implementation source code
References
Adaptive timestep implementation:
core/integration_runner.pyConfiguration dataclass:
AdaptiveTimestepConfigBatched logger:
core/batched_logger.py