Performance Notes#
ElectroSim targets smooth real-time feedback while solving an \(\mathcal{O}(N^2)\) pairwise problem. This page explains where CPU time goes, how to profile it, and which configuration knobs provide the biggest wins.
Built-in Profiling#
The desktop build instruments the main loop in main.py:
physics_ms: Time spent inelectrosim.simulation.engine.Simulation.step_framecovering RK4 substeps, force evaluation, collision handling, and trajectory bookkeeping.field_ms: Time to rasterise the electric field grid insideelectrosim.rendering.draw.draw_field_grid().draw_ms: Remaining render cost (particles, glow, trails, vectors, overlays).total_ms: End-to-end frame duration.
Enable the overlay with PROFILE_OVERLAY_ENABLED = True in
electrosim/config.py. Toggle visibility during runtime with the O key.
Values are averaged per frame, so transient spikes are easy to spot.
For deeper inspection run ElectroSim under an external profiler (e.g. py-spy)
and filter for modules under electrosim.simulation and electrosim.rendering.
Cost Drivers#
Physics#
Pairwise accelerations scale as \(\mathcal{O}(N^2)\) per substep. Kernels live in
electrosim.simulation.physicsand are JIT-compiled with Numba when available.NUMBA_PARALLEL_ACCEL = Trueactivatesprangeparallel loops, giving near linear speed-up on multi-core CPUs. Disable if you observe oversubscription or when debugging.NUMBA_FASTMATH = Truepermits aggressive floating-point simplifications. Use only when tiny energy drift is acceptable.Neutral, fixed, or massless particles are skipped to avoid unnecessary work.
Field Grid#
Complexity is \(\mathcal{O}(MN)\) where \(M\) is the number of grid samples.
FIELD_GRID_STEP_PXcontrols \(M\). Doubling the spacing roughly quarters the cost at the expense of spatial resolution.With
FIELD_SAMPLER_ENABLED = True, the grid is cache-computed once per frame (seeelectrosim.rendering.field_sampler). Disable for debugging to force per-pixel evaluation.In Pyodide builds Numba is unavailable; everything runs through the pure Python path, so consider coarser grids for browser deployments.
Rendering#
Glow: Alpha blended surfaces cached inside
electrosim.rendering.primitives.draw_glow_at_screen_pos(). The cache (_GLOW_CACHE) size is capped byGLOW_CACHE_MAX_SURFACES; lower it if memory is tight.Trails:
electrosim.rendering.trailskeeps a dedicated surface. Cost scales with sample frequency (FPS_TARGET) and history length (TRAJECTORY_HISTORY_SECONDS).Vectors: Force/velocity arrows are cheap individually but multiply by particle count. Clamp with
VECTOR_MAX_LENGTH_PXto avoid overstretching that stresses the rasteriser.
Tuning Workflow#
Characterise the baseline: Start with a realistic particle count, enable the profiling overlay, and note
physics_ms,field_ms, anddraw_ms.Manage particle count: Keep
MAX_PARTICLESmodest for interactive use. Consider scenario-specific limits if you build scripted demos.Balance timestep vs accuracy: Lower
SUBSTEPS_BASE_PER_FRAMEorFPS_TARGETto trade precision for speed. Validation mode (Ukey) is helpful to make sure the integrator still behaves.Optimise the field: Increase
FIELD_GRID_STEP_PX, stick with brightness mode (fixed-length arrows), or disable the field entirely while debugging.Trim visuals: Toggle glow (
B), trails (T), force (F) and velocity (V) vectors as needed. If glow is required, try reducingGLOW_RADIUS_SCALEfor smaller blended quads.
Batch Experiments#
The simulation orchestrator exposes all necessary hooks for scripted sweeps.
Use electrosim.simulation.engine.Simulation from a Python shell or test
to:
from electrosim.simulation.engine import Simulation
sim = Simulation()
sim.clear()
# populate custom scenario here
for _ in range(600):
sim.step_frame()
print(sim.energy_tot)
Combine this with adjustments to config to benchmark multiple parameter sets
without rendering overhead. This is especially useful when validating new force
approximations or experimenting with alternative timestep controllers.
Browser Considerations#
The Pyodide build is typically 2–5× slower than the native desktop version:
No Numba acceleration; only the pure Python kernels execute.
WebGL canvas uploads add latency for each frame.
Heavy trail histories and dense field grids quickly saturate the UI thread.
Keep FIELD_GRID_STEP_PX large (40–60), disable glow by default, and limit the
particle count to fewer than ~20 for smooth behaviour. These overrides are
captured in web/config_web.py.